2395 lines
136 KiB
Python
2395 lines
136 KiB
Python
#!/usr/bin/env python3
|
|
"""Local semantic-checker tests; synthetic examples are never game evidence."""
|
|
import copy
|
|
import ast
|
|
import unittest
|
|
import sys
|
|
import contextlib
|
|
import io
|
|
import json
|
|
import hashlib
|
|
import gzip
|
|
import struct
|
|
import zlib
|
|
import tempfile
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
sys.dont_write_bytecode = True
|
|
import check_p23_life_observation_evidence as check
|
|
|
|
|
|
def synthetic_repair28_fields(report, snapshot=None):
|
|
"""Counterfactual unit-test projection only; never rewrite raw history."""
|
|
report['overlay_passes'] = ['traffic', 'cabins', 'actors']
|
|
report['shaft_footprint'] = [list(c) for c in dict.fromkeys(
|
|
(stop[0]+dx, stop[1], stop[2]) for shaft in check.elevator_report(snapshot)['shafts']
|
|
for stop in shaft['stops'] for dx in range(max(shaft.get('width', 2), 1)))] if snapshot else []
|
|
for cabin in report.get('cabins', []):
|
|
if snapshot is not None:
|
|
shaft = next(s for s in check.elevator_report(snapshot)['shafts'] if s['shaft_id'] == cabin['shaft_id'])
|
|
cabin.update(phase=shaft['phase'], passenger_count=len(shaft['boarded']))
|
|
count, capacity = len(shaft['boarded']), shaft['capacity']
|
|
cabin['badge'] = ('運転 ' if shaft['operational'] else '停止 ') + ('満員 ' if capacity > 0 and count >= capacity else '') + f'{count}/{capacity}人' + (' 開 ' if cabin['doors_open'] else ' 閉 ') + shaft['phase']
|
|
cabin['badge_rect'] = check.badge_rectangle(cabin['badge_at'], cabin['badge'])
|
|
report['traffic_obstacles'] = check.traffic_obstacles(report)
|
|
for row in report.get('traffic', []):
|
|
row['segment_rects'] = check.traffic_segments(row['band_rect'], row['rect'], report['traffic_obstacles'])
|
|
return report
|
|
|
|
|
|
class Repair28GeometryTests(unittest.TestCase):
|
|
def test_one_screenshot_allowed_but_real_input_pair_kept(self):
|
|
check.require_screenshot_coverage('screenshot', [{}])
|
|
check.require_screenshot_coverage('click', [{}, {}])
|
|
for action, shots in [('screenshot', []), ('click', [{}]), ('wheel', [{}]), ('key', None)]:
|
|
with self.subTest(action=action), self.assertRaises(ValueError):
|
|
check.require_screenshot_coverage(action, shots)
|
|
|
|
def test_overview_occlusion_keeps_density_row_and_safe_order(self):
|
|
report = LifeOverlayTests().report()
|
|
report['actor_drawings'] = []
|
|
report['badges'] = [dict(at=[0,y], text='cover') for y in (0,14,28,42,56,70,84)]
|
|
synthetic_repair28_fields(report)
|
|
self.assertEqual(report['traffic'][0]['segment_rects'], [])
|
|
self.assertEqual(report['traffic'][0]['traffic']['current_density'], 1)
|
|
check.validate_life_overlay(report)
|
|
source = (check.ROOT/'scenes/main/world_preview.gd').read_text()
|
|
broken = source.replace('canvas.draw_rect(rect, Color("34413b"))',
|
|
'canvas.draw_rect(rect, Color("34413b", 0.5))')
|
|
with mock.patch.object(Path, 'read_text', return_value=broken):
|
|
with self.assertRaisesRegex(ValueError, 'opaque later badges'):
|
|
check.validate_life_overlay(report)
|
|
|
|
def test_r15_empty_cabin_rejects_queue_guest_even_if_marked_riding(self):
|
|
fixture = json.loads((check.ROOT/'tests/fixtures/p23-03/repair28_r15_negative.json').read_text())
|
|
cabin = dict(rect=fixture['cabin_rect'], passengers=[], passenger_count=fixture['passenger_count'])
|
|
actor = dict(fixture['actor'], riding=True, mechanical_pass=True)
|
|
with self.assertRaisesRegex(ValueError, 'non-passenger inside cabin'):
|
|
check.validate_cabin_intrusion(dict(cabins=[cabin], actor_drawings=[actor]))
|
|
cabin['passengers'] = [actor]
|
|
with self.assertRaisesRegex(ValueError, 'passenger drawn in empty cabin'):
|
|
check.validate_cabin_intrusion(dict(cabins=[cabin], actor_drawings=[]))
|
|
|
|
def test_r15_traffic_is_split_against_cabin_and_badge(self):
|
|
fixture = json.loads((check.ROOT/'tests/fixtures/p23-03/repair28_r15_negative.json').read_text())
|
|
row = fixture['traffic']
|
|
obstacles = [fixture['cabin_rect'], check.badge_rectangle(**fixture['queue_badge'])]
|
|
for obstacle in obstacles:
|
|
self.assertTrue(check.intersects(row['band_rect'], obstacle))
|
|
pieces = check.traffic_segments(row['band_rect'], row['rect'], obstacles)
|
|
self.assertTrue(pieces)
|
|
for piece in pieces:
|
|
for obstacle in obstacles:
|
|
self.assertFalse(check.intersects(piece, obstacle))
|
|
|
|
def test_exact_subtraction_and_full_cover_fallback(self):
|
|
self.assertEqual(check.traffic_segments([10,85,60,5], [0,0,80,96],
|
|
[[20,0,20,90], [45,80,20,14]]), [[10,85,10,5], [40,85,5,5], [65,85,5,5]])
|
|
self.assertEqual(check.traffic_segments([10,85,60,5], [0,0,80,96],
|
|
[[0,80,80,16]]), [[10,0,60,5]])
|
|
|
|
def test_claimed_safe_segments_and_obstacles_are_recomputed(self):
|
|
report = LifeOverlayTests().report()
|
|
report['landing_badges'] = [dict(at=[20,80], text='queue')]
|
|
synthetic_repair28_fields(report)
|
|
check.validate_life_overlay(report)
|
|
for field in ('segment_rects', 'traffic_obstacles'):
|
|
bad = copy.deepcopy(report)
|
|
if field == 'segment_rects':
|
|
bad['traffic'][0][field] = [bad['traffic'][0]['band_rect']]
|
|
else:
|
|
bad[field] = []
|
|
with self.subTest(field=field), self.assertRaisesRegex(ValueError, 'traffic (segment|obstacle) geometry'):
|
|
check.validate_life_overlay(bad)
|
|
|
|
def test_width_two_walkable_interior_is_hidden_not_a_passenger(self):
|
|
fixture = SpatialTests()
|
|
fixture.setUp()
|
|
fixture.test_missing_queue_overflow_rejected()
|
|
# The old implementation assigned x+1 to g0. Preserve the typed demand
|
|
# and assert that fabricating that old visible actor is now rejected.
|
|
fixture.report['landing_badges'][0]['overflow'] = 3
|
|
fixture.report['queue_positions'] = {'guest/g0': [100,82]}
|
|
with self.assertRaisesRegex(ValueError, 'queue_positions'):
|
|
check.compare_queues(fixture.snapshot, fixture.report)
|
|
fixture.report['queue_positions'] = {}
|
|
fixture.report['shaft_footprint'].pop(1)
|
|
with self.assertRaisesRegex(ValueError, 'shaft footprint'):
|
|
check.compare_queues(fixture.snapshot, fixture.report)
|
|
|
|
def test_cabin_state_false_fields_rejected_on_both_layers(self):
|
|
fixture = SpatialTests()
|
|
fixture.setUp()
|
|
fixture.test_cabin_geometry_and_phase()
|
|
for layer in (0,1):
|
|
fixture.report['layer'] = layer
|
|
fixture.runtime['elevators']['shafts'][0]['config']['stops'][0][2] = layer
|
|
for field, value in [('passenger_count',1), ('operational',False), ('phase','PICKUP'),
|
|
('doors_open',False), ('badge_rect',[80,58,1,14])]:
|
|
bad = copy.deepcopy(fixture.report)
|
|
bad['cabins'][0][field] = value
|
|
with self.subTest(layer=layer,field=field), self.assertRaises(ValueError):
|
|
check.compare_cabin_geometry(fixture.snapshot, bad)
|
|
|
|
|
|
class Repair28CompositionTests(unittest.TestCase):
|
|
"""No Git subprocess, repository writes, engine or proof generation."""
|
|
def setUp(self):
|
|
self.s, self.m, self.t, self.q = ('1'*40, '2'*40, '3'*40, '4'*40)
|
|
base_marker = dict(schema_version=1, rows=[dict(test='old', required=True)])
|
|
s_marker = copy.deepcopy(base_marker)
|
|
s_marker['rows'].append(dict(test='tests/ui/test_p23_life_observation.gd', required=True))
|
|
m_marker = copy.deepcopy(base_marker)
|
|
m_marker['rows'].append(dict(test='P18', required=True))
|
|
self.documents = [base_marker, s_marker, m_marker, check.marker_join(base_marker,s_marker,m_marker)]
|
|
names = ['project.godot','export_presets.cfg','Makefile','AGENTS.md', check.PLAN, check.CONTRACT,
|
|
'tests/business/test_required_kitchen.gd','tools/regression/suite-policy.json',
|
|
'scenes/main/world_preview.gd']
|
|
base = {name: (name+' baseline').encode() for name in names}
|
|
self.files = {r: copy.deepcopy(base) for r in (check.BASE_COMMIT,self.s,self.m,self.t)}
|
|
for revision, document in zip(self.files, self.documents):
|
|
self.files[revision][check.CONTRACT] = json.dumps(document).encode()
|
|
self.files[self.s]['scenes/main/world_preview.gd'] = b'S drawing'
|
|
self.files[self.m]['tools/regression/suite-policy.json'] = b'M only policy'
|
|
self.files[self.m]['tests/business/test_required_kitchen.gd'] = b'M approved QA source'
|
|
self.files[self.t].update({p:self.files[self.s][p] for p in ['scenes/main/world_preview.gd']})
|
|
self.files[self.t].update({p:self.files[self.m][p] for p in
|
|
['tools/regression/suite-policy.json','tests/business/test_required_kitchen.gd']})
|
|
self.source = {p:hashlib.sha256(v).hexdigest() for p,v in self.files[self.t].items()
|
|
if p not in (check.PLAN, check.CONTRACT)}
|
|
self.inputs = {check.CONTRACT:hashlib.sha256(self.files[self.t][check.CONTRACT]).hexdigest()}
|
|
self.roles = dict(submitted_sha=self.s, main_context_sha=self.m, tested_tree=self.t, tested_commit=self.q,
|
|
blob_bindings={p:self.blob(v) for p,v in self.files[self.t].items()})
|
|
self.merge_tree = self.t
|
|
self.calls = []
|
|
|
|
@staticmethod
|
|
def blob(value):
|
|
return hashlib.sha1(b'blob '+str(len(value)).encode()+b'\0'+value).hexdigest()
|
|
|
|
def git(self, *args):
|
|
self.calls.append(args)
|
|
if args[0] == 'cat-file': return b'tree\n' if args[2] == self.t else b'commit\n'
|
|
if args[0] == 'rev-parse': return (self.q if args[1] == 'HEAD' else self.t).encode()+b'\n'
|
|
if args[0] == 'status': return b''
|
|
if args[0] == 'merge-tree': return self.merge_tree.encode()+b'\n'
|
|
if args[0] == 'ls-tree':
|
|
return b''.join(('100644 blob '+self.blob(value)+'\t'+path+'\0').encode()
|
|
for path,value in self.files[args[-1]].items())
|
|
if args[0] == 'show':
|
|
revision,path = args[1].split(':',1)
|
|
return self.files[revision][path]
|
|
raise AssertionError(args)
|
|
|
|
def batch(self, oids):
|
|
self.calls.append(('cat-file', '--batch'))
|
|
blobs = {self.blob(v): v for files in self.files.values() for v in files.values()}
|
|
return io.BytesIO(b''.join(oid.encode()+b' blob '+str(len(blobs[oid])).encode()+b'\n'
|
|
+blobs[oid]+b'\n' for oid in oids)), 0
|
|
|
|
def verify(self):
|
|
original = Path.read_bytes
|
|
def read(path):
|
|
if path.is_relative_to(check.ROOT):
|
|
return self.files[self.t][str(path.relative_to(check.ROOT))]
|
|
return original(path)
|
|
with mock.patch.object(Path, 'read_bytes', read):
|
|
return check.verify_composition(check.ROOT,self.roles,self.source,self.inputs,self.git,
|
|
read_batch=self.batch)
|
|
|
|
def test_valid_route_b_keeps_unchanged_allowed_path_from_main(self):
|
|
self.assertEqual(self.verify()['tested_tree'], self.t)
|
|
self.assertIn(('merge-tree','--write-tree',self.s,self.m),self.calls)
|
|
|
|
def test_out_of_scope_submitted_change_rejected(self):
|
|
self.files[self.s]['autoload/simulation_runtime.gd'] = b'unauthorized'
|
|
with self.assertRaisesRegex(ValueError,'outside allowed_paths'): self.verify()
|
|
|
|
def test_wrong_merge_tree_rejected(self):
|
|
self.merge_tree = '5'*40
|
|
with self.assertRaisesRegex(ValueError,'deterministic merge-tree'): self.verify()
|
|
|
|
def test_conflicted_merge_fails_closed(self):
|
|
original = self.git
|
|
def conflict(*args):
|
|
if args[0] == 'merge-tree': raise ValueError('Git composition failed/conflicted')
|
|
return original(*args)
|
|
with self.assertRaisesRegex(ValueError,'conflicted'):
|
|
check.verify_composition(check.ROOT,self.roles,self.source,self.inputs,conflict)
|
|
|
|
def test_s_only_and_main_only_blob_provenance(self):
|
|
for path in ('scenes/main/world_preview.gd','tools/regression/suite-policy.json',
|
|
'tests/business/test_required_kitchen.gd'):
|
|
with self.subTest(path=path):
|
|
original = self.files[self.t][path]
|
|
self.files[self.t][path] = self.files[check.BASE_COMMIT][path]
|
|
with self.assertRaisesRegex(ValueError,'blob/mode provenance'): self.verify()
|
|
self.files[self.t][path] = original
|
|
|
|
def test_structural_join_preserves_all_main_rows_and_metadata(self):
|
|
base,s,m,t = self.documents
|
|
for mutation in ('delete_P18','overwrite_P18','extra','type','metadata','duplicate_P23'):
|
|
bad = copy.deepcopy(t)
|
|
if mutation == 'delete_P18': bad['rows'].pop(1)
|
|
if mutation == 'overwrite_P18': bad['rows'][1]['required'] = False
|
|
if mutation == 'extra': bad['rows'].append(dict(test='unapproved'))
|
|
if mutation == 'type': bad['schema_version'] = True
|
|
if mutation == 'metadata': bad['extra'] = 'changed'
|
|
if mutation == 'duplicate_P23': bad['rows'].append(copy.deepcopy(bad['rows'][-1]))
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
|
check.verify_marker_join(base,s,m,bad)
|
|
positioned = copy.deepcopy(t)
|
|
positioned['rows'].insert(1, positioned['rows'].pop())
|
|
check.verify_marker_join(base,s,m,positioned)
|
|
with self.assertRaisesRegex(ValueError,'duplicate JSON key'):
|
|
check.strict_json('{"rows":[],"rows":[]}')
|
|
|
|
def test_binding_completeness_and_old_source_relabel_rejected(self):
|
|
for mutation in ('missing_blob','wrong_blob','extra_blob','old_source','omitted_QA'):
|
|
original_roles, original_source = copy.deepcopy(self.roles),dict(self.source)
|
|
path = 'tests/business/test_required_kitchen.gd'
|
|
if mutation == 'missing_blob': self.roles['blob_bindings'].pop(path)
|
|
if mutation == 'wrong_blob': self.roles['blob_bindings'][path] = '0'*40
|
|
if mutation == 'extra_blob': self.roles['blob_bindings']['unbound'] = '0'*40
|
|
if mutation == 'old_source': self.source[path] = hashlib.sha256(self.files[self.s][path]).hexdigest()
|
|
if mutation == 'omitted_QA': self.source.pop(path)
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError): self.verify()
|
|
self.roles, self.source = original_roles, original_source
|
|
|
|
|
|
class Repair31StreamTests(unittest.TestCase):
|
|
def test_owned_child_cleanup_on_parse_failure_and_early_close(self):
|
|
oid = check.blob_oid(b'x')
|
|
for malformed in (True, False):
|
|
child = mock.Mock()
|
|
child.stdin = io.BytesIO()
|
|
child.stdout = io.BytesIO(b'bad\n' if malformed else oid.encode()+b' blob 1\nx\n')
|
|
child.poll.return_value = None
|
|
child.wait.return_value = -9
|
|
with mock.patch.object(subprocess, 'Popen', return_value=child):
|
|
stream = check.git_blob_stream(check.ROOT, [oid])
|
|
if malformed:
|
|
with self.assertRaises(ValueError): next(stream)
|
|
else:
|
|
self.assertEqual(next(stream), (oid, b'x'))
|
|
stream.close()
|
|
child.kill.assert_called_once_with()
|
|
child.wait.assert_called_once_with()
|
|
self.assertTrue(child.stdin.closed)
|
|
self.assertTrue(child.stdout.closed)
|
|
|
|
def test_strict_stream_negatives_and_repeated_oids(self):
|
|
content = b'abc'
|
|
oid = check.blob_oid(content)
|
|
other = check.blob_oid(b'def')
|
|
record = oid.encode()+b' blob 3\nabc\n'
|
|
def read(data, oids=(oid,), status=0):
|
|
return list(check.git_blob_stream(check.ROOT, oids,
|
|
lambda requested: (io.BytesIO(data), status)))
|
|
self.assertEqual(read(record*2, (oid, oid)), [(oid, content)]*2)
|
|
invalid = [record[:-2], record[:-1], record.replace(b'blob 3', b'blob 2'),
|
|
record.replace(b'blob 3', b'blob 4'), record.replace(b'blob', b'tree'),
|
|
oid.encode()+b' missing\n', oid.encode()+b' ambiguous\n',
|
|
record+record, record.replace(b'abc', b'def'), b'',
|
|
record.replace(oid.encode(), other.encode())]
|
|
for data in invalid:
|
|
with self.subTest(data=data), self.assertRaises(ValueError):
|
|
read(data)
|
|
with self.assertRaises(ValueError):
|
|
read(record, status=1)
|
|
with self.assertRaises(ValueError):
|
|
read(record*2, (oid, other))
|
|
|
|
def test_checkout_paths_and_modes(self):
|
|
oid = check.blob_oid(b'x')
|
|
roles = dict(tested_tree='1'*40)
|
|
for path, mode, kind, duplicate in [('a', '100644', 'blob', True),
|
|
('a','120000','blob',False), ('a','160000','commit',False),
|
|
('/a','100644','blob',False), ('a/../b','100644','blob',False)]:
|
|
record = f'{mode} {kind} {oid}\t{path}\0'.encode()
|
|
with self.subTest(path=path, mode=mode), self.assertRaises(ValueError):
|
|
with check.tested_checkout(check.ROOT, roles,
|
|
lambda *args: record*(2 if duplicate else 1),
|
|
lambda oids: (io.BytesIO(oid.encode()+b' blob 1\nx\n'),0)):
|
|
self.fail('unsafe checkout yielded')
|
|
|
|
def test_view_authority_and_binding_recheck(self):
|
|
fixture = Repair28CompositionTests()
|
|
fixture.setUp()
|
|
def verify(view):
|
|
return check.verify_composition(check.ROOT, fixture.roles, fixture.source,
|
|
fixture.inputs, fixture.git, view=view, read_batch=fixture.batch)
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
with self.assertRaisesRegex(ValueError, 'unregistered'):
|
|
verify(Path(temporary))
|
|
with check.tested_checkout(check.ROOT, fixture.roles, fixture.git, fixture.batch) as view:
|
|
registered = check._VERIFIED_VIEWS[view.resolve()]
|
|
check._VERIFIED_VIEWS[view.resolve()] = ('0'*40, registered[1])
|
|
with self.assertRaisesRegex(ValueError, 'tree mismatch'): verify(view)
|
|
check._VERIFIED_VIEWS[view.resolve()] = (registered[0], {})
|
|
with self.assertRaisesRegex(ValueError, 'manifest mismatch'): verify(view)
|
|
check._VERIFIED_VIEWS[view.resolve()] = registered
|
|
# A normative document is outside source_refs; its bytes must still be checked.
|
|
(view/check.PLAN).write_bytes(b'tampered')
|
|
with mock.patch.object(Path, 'read_bytes', autospec=True) as reader:
|
|
# Preserve real view reads and synthesize only the source checkout comparison.
|
|
original = io.open
|
|
def read(path):
|
|
if path.is_relative_to(view):
|
|
with original(path, 'rb') as stream: return stream.read()
|
|
return fixture.files[fixture.t][str(path.relative_to(check.ROOT))]
|
|
reader.side_effect = read
|
|
with self.assertRaisesRegex(ValueError, 'SHA-1 mismatch'): verify(view)
|
|
self.assertNotIn(view.resolve(), check._VERIFIED_VIEWS)
|
|
|
|
def test_constant_calls_full_composition_with_5000_paths(self):
|
|
fixture = Repair28CompositionTests()
|
|
fixture.setUp()
|
|
additions = {f'assets/bulk/{i:05}.txt': b'repeated small blob' for i in range(5000)}
|
|
for files in fixture.files.values(): files.update(additions)
|
|
fixture.source.update({p: hashlib.sha256(v).hexdigest() for p,v in additions.items()})
|
|
fixture.roles['blob_bindings'].update({p: check.blob_oid(v) for p,v in additions.items()})
|
|
original = Path.read_bytes
|
|
verified = set()
|
|
def read(path):
|
|
if path.is_relative_to(check.ROOT):
|
|
return fixture.files[fixture.t][str(path.relative_to(check.ROOT))]
|
|
data = original(path)
|
|
if 'bulk' in path.parts:
|
|
self.assertEqual(data, b'repeated small blob')
|
|
verified.add(path.name)
|
|
return data
|
|
with mock.patch.object(Path, 'read_bytes', read):
|
|
with check.tested_checkout(check.ROOT, fixture.roles, fixture.git, fixture.batch) as view:
|
|
check.verify_composition(check.ROOT, fixture.roles, fixture.source, fixture.inputs,
|
|
fixture.git, view=view, read_batch=fixture.batch)
|
|
self.assertEqual(len(verified), 5000)
|
|
self.assertEqual(fixture.calls.count(('cat-file','--batch')), 1)
|
|
self.assertEqual(len([c for c in fixture.calls if c[0] == 'show']), 4)
|
|
|
|
def test_real_pipe_overflow_requests_and_repeated_paths(self):
|
|
runtime = os.environ.get('TMPDIR')
|
|
self.assertTrue(runtime, 'assigned TMPDIR required')
|
|
with tempfile.TemporaryDirectory(dir=runtime, prefix='p23-repair31-') as temporary:
|
|
repo = Path(temporary)
|
|
def git(*args, data=None):
|
|
return subprocess.run(['git','-C',str(repo),*args], input=data,
|
|
capture_output=True, check=True, timeout=30).stdout
|
|
git('init','-q')
|
|
content = b'pipe pacing verified\n'
|
|
oid = git('hash-object','-w','--stdin', data=content).decode().strip()
|
|
count = 5000
|
|
tree = git('mktree', data=b''.join(
|
|
f'100644 blob {oid}\tpath-{i:05}\n'.encode() for i in range(count))).decode().strip()
|
|
# A watchdog bounds a deadlock regression; elapsed time is not a pass metric.
|
|
program = '''import sys
|
|
from pathlib import Path
|
|
sys.path.insert(0, sys.argv[1])
|
|
import check_p23_life_observation_evidence as c
|
|
repo, tree = Path(sys.argv[2]), sys.argv[3]
|
|
with c.tested_checkout(repo, {'tested_tree': tree}) as view:
|
|
paths = list(view.iterdir())
|
|
assert len(paths) == 5000
|
|
assert all(p.read_bytes() == b'pipe pacing verified\\n' for p in paths)
|
|
'''
|
|
subprocess.run([sys.executable, '-B', '-c', program, str(check.ROOT/'tools'),
|
|
str(repo), tree], check=True, timeout=60)
|
|
|
|
|
|
class Repair30EntryTests(unittest.TestCase):
|
|
"""Synthetic labelled Git history; zero native or OS credit."""
|
|
def setUp(self):
|
|
runtime = os.environ.get('TMPDIR')
|
|
self.assertTrue(runtime, 'assigned TMPDIR required')
|
|
self.temporary = tempfile.TemporaryDirectory(dir=runtime, prefix='p23-repair30-')
|
|
self.addCleanup(self.temporary.cleanup)
|
|
self.repo = Path(self.temporary.name)/'repo'
|
|
self.repo.mkdir()
|
|
self.build = Path(self.temporary.name)/'build'
|
|
self.evidence = self.build/'p23-life-observation-evidence'
|
|
self.evidence.mkdir(parents=True)
|
|
self.git('init', '-q')
|
|
self.git('config', 'user.name', 'Synthetic fixture')
|
|
self.git('config', 'user.email', 'fixture@example.invalid')
|
|
for path in ('tools/run_all_tests.py', 'tests/fixtures/p23-03/observation.gd',
|
|
check.PLAN, check.SPEC, 'docs/traceability/original/BASELINE.txt'):
|
|
self.write(path, (check.ROOT/path).read_bytes())
|
|
for path in ('project.godot', 'export_presets.cfg', 'Makefile', 'AGENTS.md',
|
|
'scenes/main/world_preview.gd', 'tests/business/test_required_kitchen.gd',
|
|
'tools/regression/suite-policy.json'):
|
|
self.write(path, 'synthetic baseline\n')
|
|
for i in range(220):
|
|
self.write(f'assets/repair31/{i:04}.txt', f'verified path {i}\n')
|
|
self.marker = dict(schema_version=1, rows=[dict(test='history-'+str(i), required=True) for i in range(6)])
|
|
self.write_marker(self.marker)
|
|
self.base = self.commit('synthetic base')
|
|
submitted = copy.deepcopy(self.marker)
|
|
submitted['rows'].insert(0, dict(test='tests/ui/test_p23_life_observation.gd', required=True))
|
|
self.write_marker(submitted)
|
|
self.write('scenes/main/world_preview.gd', 'synthetic S\n')
|
|
self.s = self.commit('synthetic S')
|
|
self.git('checkout', '-q', '--detach', self.base)
|
|
main = copy.deepcopy(self.marker)
|
|
main['rows'].append(dict(test='P18-and-kitchen-QA', required=True))
|
|
self.write_marker(main)
|
|
self.write('tests/business/test_required_kitchen.gd', 'synthetic M QA\n')
|
|
self.m = self.commit('synthetic M')
|
|
self.t = self.git('merge-tree', '--write-tree', self.s, self.m).splitlines()[0]
|
|
self.q = self.git('commit-tree', self.t, '-p', self.s, '-m', 'synthetic Q, no evidence')
|
|
self.c = self.git('commit-tree', self.t, '-p', self.m, '-p', self.s, '-m', 'synthetic C')
|
|
self.git('checkout', '-q', '--detach', self.q)
|
|
runner = check.module('p23_synthetic_source_filter', check.ROOT/'tools/run_all_tests.py')
|
|
sources = {p: row['sha256'] for p, row in runner.source_refs(self.repo).items()}
|
|
inputs = check.input_map(self.repo)
|
|
required = set(sources) | {p.split('#')[0] for p in inputs} | {check.PLAN, check.CONTRACT,
|
|
'project.godot', 'tests/business/test_required_kitchen.gd', 'tools/regression/suite-policy.json'}
|
|
self.roles = dict(submitted_sha=self.s, main_context_sha=self.m, tested_tree=self.t,
|
|
tested_commit=self.q, blob_bindings={p:self.git('rev-parse', self.t+':'+p) for p in required})
|
|
self.index = dict(schema='p23-life-observation-v2', source_map=sources, input_map=inputs,
|
|
composition={k:self.roles[k] for k in ('submitted_sha','main_context_sha','tested_tree','tested_commit')},
|
|
commit=self.q, spec_sha256=check.SPEC_SHA,
|
|
marker_contract_sha256=check.sha(self.repo/check.CONTRACT), artifacts={}, phases=[])
|
|
self.publish()
|
|
self.git('checkout', '-q', '--detach', self.s)
|
|
self.patch = mock.patch.object(check, 'BASE_COMMIT', self.base)
|
|
self.patch.start()
|
|
self.addCleanup(self.patch.stop)
|
|
|
|
def git(self, *args):
|
|
result = subprocess.run(['git', '-C', str(self.repo), *args], capture_output=True, check=True)
|
|
return result.stdout.decode().strip()
|
|
|
|
def write(self, path, content):
|
|
target = self.repo/path
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_bytes(content.encode() if isinstance(content, str) else content)
|
|
|
|
def write_marker(self, value):
|
|
self.write(check.CONTRACT, json.dumps(value, indent=2)+'\n')
|
|
|
|
def commit(self, message):
|
|
self.git('add', '.')
|
|
self.git('commit', '-q', '-m', message)
|
|
return self.git('rev-parse', 'HEAD')
|
|
|
|
def publish(self):
|
|
context = self.evidence/'root-composition.json'
|
|
context.write_text(json.dumps(self.roles))
|
|
self.index['root_composition_sha256'] = check.sha(context)
|
|
(self.evidence/'index.json').write_text(json.dumps(self.index))
|
|
|
|
def cli(self, verify=True, extra_env=None, extra_args=()):
|
|
env = dict(BUILD_OUTPUT_DIR=str(self.build))
|
|
env.update(extra_env or {})
|
|
with mock.patch.dict(os.environ, env, clear=True), contextlib.redirect_stdout(io.StringIO()) as output:
|
|
code = check.main((['--verify-only'] if verify else []) + ['--project-root', str(self.repo)] + list(extra_args))
|
|
self.assertEqual(code, 1)
|
|
result = json.loads(output.getvalue())
|
|
self.assertEqual(result['acceptance'], 'pending')
|
|
return result['deficits']
|
|
|
|
def assert_entry(self, verify=True):
|
|
deficits = self.cli(verify)
|
|
self.assertTrue(any('native evidence deficit' in d for d in deficits), deficits)
|
|
self.assertTrue(any('missing OS A-writer process receipt' in d for d in deficits), deficits)
|
|
for condition in check.CONDITIONS:
|
|
self.assertTrue(any(d.startswith(condition+':') for d in deficits), deficits)
|
|
self.assertFalse(any('invalid evidence index' in d for d in deficits), deficits)
|
|
|
|
def test_normal_and_verify_only_S_Q_C_keep_all_seven_blockers(self):
|
|
for head in (self.s, self.q, self.c):
|
|
self.git('checkout', '-q', '--detach', head)
|
|
for verify in (False, True):
|
|
with self.subTest(head=head, verify=verify):
|
|
self.assert_entry(verify)
|
|
|
|
def test_whole_entry_has_constant_git_calls_and_verifies_every_path(self):
|
|
spawn = subprocess.Popen
|
|
calls, read_paths = [], set()
|
|
original = Path.read_bytes
|
|
def counted(*args, **kwargs):
|
|
calls.append(args[0])
|
|
return spawn(*args, **kwargs)
|
|
def read(path):
|
|
data = original(path)
|
|
if 'repair31' in path.parts:
|
|
self.assertEqual(data, f'verified path {int(path.stem)}\n'.encode())
|
|
read_paths.add(path.name)
|
|
return data
|
|
with mock.patch.object(subprocess, 'Popen', counted), mock.patch.object(Path, 'read_bytes', read):
|
|
self.assert_entry()
|
|
self.assertEqual(len(read_paths), 220)
|
|
self.assertEqual(sum(command[-2:] == ['cat-file','--batch'] for command in calls), 1)
|
|
self.assertEqual(sum('show' in command for command in calls), 4)
|
|
|
|
def test_wrong_roles_and_missing_Q(self):
|
|
for key, value in (('submitted_sha',self.base), ('main_context_sha',self.base),
|
|
('tested_tree',self.git('rev-parse', self.base+'^{tree}')),
|
|
('tested_commit','0'*40)):
|
|
original = self.roles[key]
|
|
self.roles[key] = value
|
|
self.publish()
|
|
with self.subTest(key=key):
|
|
self.assertTrue(any('invalid evidence index' in d for d in self.cli()))
|
|
self.roles[key] = original
|
|
|
|
def test_dirty_roles_and_source_QA_rollback(self):
|
|
for head in (self.s,self.q,self.c):
|
|
self.git('checkout', '-q', '--detach', head)
|
|
for path in ('scenes/main/world_preview.gd','tests/business/test_required_kitchen.gd'):
|
|
original = (self.repo/path).read_bytes()
|
|
self.write(path, 'synthetic rollback\n')
|
|
with self.subTest(head=head,path=path):
|
|
self.assertTrue(any('dirty' in d for d in self.cli()))
|
|
self.write(path, original)
|
|
|
|
def test_unexpected_C_parents_and_tree(self):
|
|
wrong_tree = self.git('rev-parse',self.base+'^{tree}')
|
|
for tree, parents in ((self.t,[self.s,self.m]), (self.t,[self.m,self.s,self.base]),
|
|
(self.t,[self.m]), (wrong_tree,[self.m,self.s])):
|
|
args = ['commit-tree', tree, '-m', 'synthetic invalid C']
|
|
for parent in parents:
|
|
args.extend(['-p',parent])
|
|
head = self.git(*args)
|
|
self.git('checkout','-q','--detach',head)
|
|
with self.subTest(tree=tree,parents=parents):
|
|
self.assertTrue(any('C parents' in d or 'C tree' in d for d in self.cli()))
|
|
|
|
def test_context_missing_mismatch_symlink_and_environment_ignored(self):
|
|
context = self.evidence/'root-composition.json'
|
|
original = context.read_bytes()
|
|
context.unlink()
|
|
self.assertTrue(any('context' in d for d in self.cli(extra_env={'P23_ROOT_COMPOSITION':str(self.repo/'unused')})))
|
|
context.write_bytes(original+b' ')
|
|
self.assertTrue(any('SHA mismatch' in d for d in self.cli()))
|
|
context.unlink()
|
|
target = Path(self.temporary.name)/'context.json'
|
|
target.write_bytes(original)
|
|
context.symlink_to(target)
|
|
self.assertTrue(any('unsafe' in d for d in self.cli()))
|
|
context.unlink()
|
|
context.write_bytes(original)
|
|
alternate = Path(self.temporary.name)/'alternate.json'
|
|
alternate.write_text('{}')
|
|
self.assertTrue(any('disagrees' in d for d in self.cli(extra_args=['--root-composition',str(alternate)])))
|
|
self.assertTrue(any('native evidence deficit' in d for d in self.cli(extra_env={'P23_ROOT_COMPOSITION':str(alternate)})))
|
|
self.index.pop('root_composition_sha256')
|
|
(self.evidence/'index.json').write_text(json.dumps(self.index))
|
|
self.assertTrue(any('SHA mismatch' in d for d in self.cli()))
|
|
|
|
|
|
class ActorNonoverlapTests(unittest.TestCase):
|
|
def report(self, second_at):
|
|
return dict(actor_drawings=[
|
|
dict(key='guest/a', at=[0, 0], scale=1, mode='idle', frame=0, flip=False),
|
|
dict(key='staff/b', at=second_at, scale=1, mode='idle', frame=0, flip=False)], cabins=[])
|
|
|
|
def test_edge_contact_is_not_overlap(self):
|
|
for at in ([6, 0], [0, 12], [-6, 0], [0, -12]):
|
|
with self.subTest(at=at):
|
|
check.validate_actor_nonoverlap(self.report(at))
|
|
|
|
def test_overlap_ignores_asserted_verdicts_and_offscreen_labels(self):
|
|
report = self.report([5, 11])
|
|
for actor in report['actor_drawings']:
|
|
actor.update(mechanical_pass=True, eligibility='offscreen', rect=[999, 999, 1, 1])
|
|
with self.assertRaisesRegex(ValueError, 'actor-actor core overlap: guest/a / staff/b'):
|
|
check.validate_actor_nonoverlap(report)
|
|
|
|
def test_cabin_passenger_is_checked_against_floor_actor(self):
|
|
report = self.report([0, 0])
|
|
report['cabins'] = [dict(passengers=[report['actor_drawings'].pop()])]
|
|
with self.assertRaisesRegex(ValueError, 'actor-actor core overlap'):
|
|
check.validate_actor_nonoverlap(report)
|
|
|
|
def test_invalid_footprints_are_not_silently_skipped(self):
|
|
for scale in (0, -1, float('nan'), float('inf'), True):
|
|
report = self.report([100, 100])
|
|
report['actor_drawings'][0]['scale'] = scale
|
|
with self.subTest(scale=scale), self.assertRaisesRegex(ValueError, 'invalid actor footprint'):
|
|
check.validate_actor_nonoverlap(report)
|
|
|
|
def test_r13_false_row_11_overlaps_staff_row_29(self):
|
|
fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/r13_actor_overlap.json').read_text())
|
|
self.assertEqual(fixture['independent_judgment'], [11, False, False, False])
|
|
with self.assertRaisesRegex(ValueError,
|
|
'actor-actor core overlap: guest/guest-auto-00000041 / staff/staff_gina'):
|
|
check.validate_actor_nonoverlap(fixture['report_inputs'])
|
|
# Exercise the actual offline evidence gate, before any audit assertions.
|
|
with self.assertRaisesRegex(ValueError, 'actor-actor core overlap'):
|
|
check.validate_drawing_rows(fixture['report_inputs'], {'rows': []})
|
|
|
|
def test_overlapping_full_boxes_with_disjoint_cores_pass(self):
|
|
# Both 16x18 boxes overlap by 9x18, but their six-pixel cores do not.
|
|
check.validate_actor_nonoverlap(self.report([7, 0]))
|
|
|
|
def test_walk_bob_is_part_of_core(self):
|
|
report = self.report([0, 12])
|
|
report['actor_drawings'][1].update(mode='walk', frame=2)
|
|
with self.assertRaisesRegex(ValueError, 'actor-actor core overlap'):
|
|
check.validate_actor_nonoverlap(report)
|
|
report['actor_drawings'][1]['frame'] = 3
|
|
check.validate_actor_nonoverlap(report)
|
|
|
|
def test_flipped_core_and_lying_breathing_bounds(self):
|
|
actor = self.report([0, 0])['actor_drawings'][0]
|
|
actor.update(flip=True, scale=3)
|
|
self.assertEqual(check.actor_identity_core(actor), [12, 3, 18, 36])
|
|
actor.update(mode='standard_room_sleep', frame=2)
|
|
self.assertEqual(check.actor_identity_core(actor), [3, 30, 39, 18])
|
|
actor['frame'] = 5
|
|
self.assertEqual(check.actor_identity_core(actor), [3, 33, 39, 15])
|
|
actor.update(mode='collapsed', flip=False)
|
|
self.assertEqual(check.actor_identity_core(actor), [6, 33, 39, 15])
|
|
|
|
def test_missing_pose_is_rejected(self):
|
|
report = self.report([100, 0])
|
|
del report['actor_drawings'][0]['frame']
|
|
with self.assertRaisesRegex(ValueError, 'missing actor pose'):
|
|
check.validate_actor_nonoverlap(report)
|
|
|
|
def test_duplicate_actor_is_rejected(self):
|
|
report = self.report([100, 0])
|
|
report['actor_drawings'][1]['key'] = 'guest/a'
|
|
with self.assertRaisesRegex(ValueError, 'duplicate actor drawing'):
|
|
check.validate_actor_nonoverlap(report)
|
|
|
|
def test_forged_transit_flag_does_not_exempt_overlap(self):
|
|
report = self.report([0, 0])
|
|
report['actor_drawings'][1]['in_transit'] = True
|
|
with self.assertRaisesRegex(ValueError, 'actor-actor core overlap'):
|
|
check.validate_actor_nonoverlap(report)
|
|
|
|
def test_core_table_requires_exact_art_source(self):
|
|
with mock.patch.object(check, 'PIXEL_PEOPLE_CORE_SHA256', '0' * 64):
|
|
with self.assertRaisesRegex(ValueError, 'PixelPeople core source SHA256'):
|
|
check.validate_actor_nonoverlap(self.report([100, 0]))
|
|
|
|
def test_recorded_service_region_and_logical_cell_have_no_common_area(self):
|
|
# Diagnostic of the unresolved simultaneous-bounds requirement, not
|
|
# permission to hide these occupants or bypass containment checks.
|
|
fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/r13_service_region_gap.json').read_text())
|
|
for guest in fixture['guests']:
|
|
traffic = next(r for r in fixture['traffic'] if r['cell'] == guest['cell'])
|
|
x, y, w, h = traffic['rect']
|
|
cell = [x - 2, y - 2, w + 4, h + 4] # get_cell_rect(...).grow(-2)
|
|
pixel = cell[2] / 16
|
|
x, y, w, h = fixture['room_rects'][guest['target']]
|
|
region = [x + pixel, y + pixel, w - 2 * pixel, h - 2 * pixel]
|
|
with self.subTest(guest=guest['id']):
|
|
self.assertLessEqual(min(cell[0] + cell[2], region[0] + region[2])
|
|
- max(cell[0], region[0]), 0)
|
|
|
|
|
|
class LifeOverlayTests(unittest.TestCase):
|
|
def report(self):
|
|
return synthetic_repair28_fields(dict(observation_serial=7, overlay_consumed_serial=7,
|
|
overlay_passes=['traffic', 'actors'], overlay_child_indices=[0, 1, 2],
|
|
actor_drawings=[dict(key='guest/a', at=[20, 30], scale=3,
|
|
mode='idle', frame=0, flip=False)], cabins=[],
|
|
traffic=[dict(rect=[2, 2, 60, 92], band_rect=[9.2, 85.5, 45.6, 5],
|
|
traffic=dict(current_density=1, temporary_block=False))]))
|
|
|
|
def test_front_and_back_valid_consumed_band(self):
|
|
for layer in (0, 1):
|
|
report = self.report()
|
|
report['layer'] = layer
|
|
check.validate_life_overlay(report)
|
|
|
|
def test_stale_serial_fails(self):
|
|
report = self.report()
|
|
report['overlay_consumed_serial'] = 6
|
|
with self.assertRaisesRegex(ValueError, 'stale LifeOverlay serial'):
|
|
check.validate_life_overlay(report)
|
|
|
|
def test_band_after_actors_fails(self):
|
|
report = self.report()
|
|
report['overlay_passes'].reverse()
|
|
with self.assertRaisesRegex(ValueError, 'LifeOverlay pass order'):
|
|
check.validate_life_overlay(report)
|
|
|
|
def test_overlay_below_inactive_art_fails(self):
|
|
report = self.report()
|
|
report['overlay_child_indices'] = [1, 0, 2]
|
|
with self.assertRaisesRegex(ValueError, 'LifeOverlay child order'):
|
|
check.validate_life_overlay(report)
|
|
|
|
def test_core_crossed_by_actual_band_fails(self):
|
|
report = self.report()
|
|
report['actor_drawings'][0]['at'][1] = 60
|
|
with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band'):
|
|
check.validate_life_overlay(report)
|
|
|
|
def test_forged_band_geometry_fails(self):
|
|
report = self.report()
|
|
report['traffic'][0]['band_rect'][1] = 50
|
|
with self.assertRaisesRegex(ValueError, 'traffic band rectangle'):
|
|
check.validate_life_overlay(report)
|
|
|
|
def test_report_cannot_excuse_wrong_source_order(self):
|
|
source = (check.ROOT / 'scenes/main/world_preview.gd').read_text()
|
|
# Claimed pass order stays valid; actual callback source is inverted.
|
|
broken = source.replace('_draw_congestion_overlay(_life_overlay)', 'SWAP')
|
|
broken = broken.replace('_draw_actors(_life_overlay)', '_draw_congestion_overlay(_life_overlay)')
|
|
broken = broken.replace('SWAP', '_draw_actors(_life_overlay)')
|
|
with mock.patch.object(Path, 'read_text', return_value=broken):
|
|
with self.assertRaisesRegex(ValueError, 'LifeOverlay source pass order'):
|
|
check.validate_life_overlay(self.report())
|
|
|
|
|
|
class ProjectionBandTests(unittest.TestCase):
|
|
def setUp(self):
|
|
fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/repair28_frame180_projection.json').read_text())
|
|
self.report, self.snapshot = fixture['report'], fixture['snapshot']
|
|
synthetic_repair28_fields(self.report, self.snapshot)
|
|
|
|
def occupied(self):
|
|
for row in self.report['traffic']:
|
|
if row['cell'] in ([0, 1, 0], [1, 1, 0]):
|
|
self.assertEqual(row['traffic']['current_density'], 0)
|
|
row['traffic']['current_density'] = 1
|
|
|
|
def test_counterfactual_actual_frame180_density_one(self):
|
|
"""COUNTERFACTUAL density substitution; never occupied-band engine evidence."""
|
|
self.occupied()
|
|
for key in ('guest/p23-018', 'guest/p23-019'):
|
|
self.assertTrue(any(a['key'] == key for c in self.report['cabins'] for a in c['passengers']))
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
|
|
def test_cabin_passenger_union_fully_occludes_band(self):
|
|
self.occupied()
|
|
row = next(r for r in self.report['traffic'] if r['cell'] == [0, 1, 0])
|
|
x, y, w, h = row['band_rect']
|
|
# Each core covers only part; their touching intervals cover the union.
|
|
for i, actor in enumerate(self.report['cabins'][0]['passengers']):
|
|
actor.update(scale=2, flip=False, mode='idle', at=[x+i*12-12, y-2])
|
|
with self.assertRaisesRegex(ValueError, 'traffic band fully occluded:'):
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
|
|
def test_spoofed_cabin_rectangle_fails_raw_recomputation(self):
|
|
self.occupied()
|
|
self.report['cabins'][0]['rect'][1] += 1
|
|
with self.assertRaisesRegex(ValueError, 'cabin/rect'):
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
|
|
def test_flag_only_cabin_projection_without_raw_membership_fails(self):
|
|
self.occupied()
|
|
for cabin in self.report['cabins']:
|
|
for actor in cabin['passengers']:
|
|
actor['key'] = 'guest/spoof-' + actor['key']
|
|
actor['riding'] = True
|
|
with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band'):
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
|
|
def test_no_raw_state_never_exempts_cabin_projection(self):
|
|
self.occupied()
|
|
with self.assertRaisesRegex(ValueError, 'floor actor core over traffic band'):
|
|
check.validate_life_overlay(self.report)
|
|
|
|
def test_floor_copy_of_raw_cabin_member_keeps_zero_intersection(self):
|
|
self.occupied()
|
|
self.report['actor_drawings'].append(copy.deepcopy(self.report['cabins'][0]['passengers'][0]))
|
|
with self.assertRaisesRegex(ValueError, 'non-passenger inside cabin'):
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
|
|
def test_blocked_band_uses_same_projection_and_legibility_rules(self):
|
|
row = next(r for r in self.report['traffic'] if r['cell'] == [0, 1, 0])
|
|
row['traffic']['temporary_block'] = True
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
x, y, w, h = row['band_rect']
|
|
for i, actor in enumerate(self.report['cabins'][0]['passengers']):
|
|
actor.update(scale=2, flip=False, mode='idle', at=[x+i*12-12, y-2])
|
|
with self.assertRaisesRegex(ValueError, 'traffic band fully occluded:'):
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
|
|
def test_flag_only_carried_projection_without_binding_fails(self):
|
|
self.occupied()
|
|
actor = copy.deepcopy(self.report['cabins'][0]['passengers'][0])
|
|
actor.update(key='staff/spoof', staff=True, riding=False, region='carried')
|
|
self.report['actor_drawings'].append(actor)
|
|
self.report['carried_patients'] = {'spoof': 'carrier'}
|
|
with self.assertRaisesRegex(ValueError, 'non-passenger inside cabin: staff/spoof'):
|
|
check.validate_life_overlay(self.report, snapshot=self.snapshot)
|
|
|
|
def test_carried_projection_requires_original_binding_tracks_and_fit(self):
|
|
fixture = AnchoredGeometryTests()
|
|
fixture.setUp()
|
|
fixture.person('staff/carrier', [0, 0, 0], 'AVAILABLE')
|
|
fixture.person('staff/patient', [0, 0, 0], 'INCAPACITATED')
|
|
fixture.runtime['staff']['tasks'] = [dict(task_type='rescue_transport', state='MOVING',
|
|
assigned_staff_id='carrier', target_entity_id='patient', rescue_facility_id='')]
|
|
fixture.report['carried_patients'] = {'patient': 'carrier'}
|
|
fixture.drawing('staff/carrier', [108, 214])
|
|
fixture.drawing('staff/patient', [108, 166], 'collapsed')
|
|
for key, value in LifeOverlayTests().report().items():
|
|
if key.startswith('overlay_') or key == 'observation_serial':
|
|
fixture.report[key] = value
|
|
# Synthetic band crosses the raised patient, with uncovered span.
|
|
fixture.report['traffic'] = [dict(cell=[0, 1, 0], rect=[78, 100.5, 60, 92],
|
|
band_rect=[85.2, 184, 45.6, 5], traffic=dict(current_density=1, temporary_block=False))]
|
|
synthetic_repair28_fields(fixture.report)
|
|
check.validate_life_overlay(fixture.report, snapshot=fixture.snapshot)
|
|
for mutation in ('binding', 'track', 'position'):
|
|
report, snapshot = copy.deepcopy(fixture.report), copy.deepcopy(fixture.snapshot)
|
|
if mutation == 'binding': snapshot['systems']['runtime']['staff']['tasks'] = []
|
|
if mutation == 'track': report['actor_tracks']['staff/carrier']['cell'] = [1, 0, 0]
|
|
if mutation == 'position': report['actor_drawings'][1]['at'][1] += 1
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
|
check.validate_life_overlay(report, snapshot=snapshot)
|
|
|
|
|
|
class SetupBudgetTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.source = (check.ROOT / 'tests/fixtures/p23-03/observation.gd').read_text()
|
|
|
|
def test_all_planned_spends_enter_common_needed_and_deltas(self):
|
|
self.assertEqual(check.check_setup_budget_source(self.source),
|
|
{'construction', 'signing', 'purchase'})
|
|
|
|
def test_missing_spend_category_fails_before_os_replay(self):
|
|
expressions = {
|
|
'construction': 'needed[key] += int(definition.construction_cost.get(key, 0))',
|
|
'signing': 'needed[&"gold"] += signing_total',
|
|
'purchase': 'needed[&"gold"] += purchase_total',
|
|
}
|
|
for category, expression in expressions.items():
|
|
with self.subTest(category=category), self.assertRaisesRegex(ValueError, category):
|
|
check.check_setup_budget_source(self.source.replace(expression, 'pass'))
|
|
|
|
def test_missing_delta_coverage_fails(self):
|
|
with self.assertRaisesRegex(ValueError, 'deltas'):
|
|
check.check_setup_budget_source(self.source.replace(
|
|
'deltas[key] = needed[key] - state.get_resource(key)', 'pass'))
|
|
|
|
|
|
class QueuedElevatorTests(unittest.TestCase):
|
|
def setUp(self):
|
|
fixture = json.loads((check.ROOT / 'tests/fixtures/p23-03/queued_elevator.json').read_text())
|
|
self.state = {'systems': {'runtime': {'elevators': fixture['saved']}}}
|
|
|
|
def test_queued_trip_without_cabin_seat_uses_config_shaft(self):
|
|
saved = self.state['systems']['runtime']['elevators']
|
|
self.assertNotIn('shaft_id', saved['shafts'][0])
|
|
self.assertEqual(saved['shafts'][0]['boarded'], [])
|
|
report = check.elevator_report(self.state)
|
|
self.assertEqual(report['shafts'][0]['waiting_count'], 1)
|
|
self.assertEqual(report['shafts'][0]['passenger_count'], 0)
|
|
self.assertAlmostEqual(report['shafts'][0]['visual_floor'], .9)
|
|
self.assertEqual(report['trips'], saved['trips'])
|
|
|
|
def test_tampered_shaft_attribution_fails(self):
|
|
self.state['systems']['runtime']['elevators']['trips'][0]['shaft_id'] = 'wrong.shaft'
|
|
with self.assertRaisesRegex(ValueError, 'shaft attribution'):
|
|
check.elevator_report(self.state)
|
|
|
|
def test_wrong_waiting_count_fails_whole_report_comparison(self):
|
|
expected = check.elevator_report(self.state)
|
|
tampered = copy.deepcopy(expected)
|
|
tampered['shafts'][0]['waiting_count'] = 0
|
|
directory = mock.MagicMock()
|
|
manifest = dict(label='queued-fixture', pid=123, state='state',
|
|
state_after='state', report='report')
|
|
directory.__truediv__.return_value.open.return_value = io.StringIO(json.dumps(manifest)+'\n')
|
|
# Exercise the actual stream comparison; isolate unrelated actor fields.
|
|
with mock.patch.object(check, 'read_object', side_effect=[self.state, self.state,
|
|
{'elevators': tampered}]), \
|
|
mock.patch.object(check, 'compare_actor_rows', return_value=[]):
|
|
with self.assertRaisesRegex(ValueError, 'typed elevator report mismatch'):
|
|
check.inspect_stream(directory)
|
|
|
|
def test_extra_and_missing_trip_keys_fail(self):
|
|
for mutation in ('extra', 'missing'):
|
|
with self.subTest(mutation=mutation):
|
|
state = copy.deepcopy(self.state)
|
|
trip = state['systems']['runtime']['elevators']['trips'][0]
|
|
if mutation == 'extra':
|
|
trip['unexpected'] = True
|
|
else:
|
|
del trip['shaft_id']
|
|
with self.assertRaisesRegex(ValueError, 'exact elevator trip keys'):
|
|
check.elevator_report(state)
|
|
|
|
|
|
class SnapshotCellTests(unittest.TestCase):
|
|
def test_layout_arrival_compares_serialized_cell_arrays(self):
|
|
source = (check.ROOT / 'tests/fixtures/p23-03/layout_comparison.gd').read_text()
|
|
serializer = (check.ROOT / 'scripts/guest/guest_runtime.gd').read_text()
|
|
self.assertIn('&"current_cell": _cell_to_array(current_cell)', serializer)
|
|
goal = check.re.search(r'const GOAL := Vector3i\(([^)]+)\)', source)
|
|
self.assertIsNotNone(goal)
|
|
coordinates = ast.literal_eval('[' + goal.group(1) + ']')
|
|
comparison = check.re.search(r'if guest\[&"current_cell"\] != (.+?) or finished\.has', source)
|
|
self.assertIsNotNone(comparison)
|
|
expression = comparison.group(1)
|
|
self.assertEqual(expression, '[GOAL.x, GOAL.y, GOAL.z]')
|
|
for axis, value in zip('xyz', coordinates):
|
|
expression = expression.replace('GOAL.' + axis, str(value))
|
|
target = ast.literal_eval(expression)
|
|
self.assertEqual(json.loads('[2, 1, 0]'), target)
|
|
for cell in ([2, 0, 0], [3, 1, 0], [2, 1, 1]):
|
|
self.assertNotEqual(cell, target)
|
|
|
|
|
|
class ImageTests(unittest.TestCase):
|
|
def test_collector_conversion_is_bound_to_each_cua_screenshot(self):
|
|
runner = check.module('p23_unit_collector', check.ROOT/'tools/run_p23_life_observation.py')
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
root = Path(temporary)
|
|
pair = root/'os-pair/A'
|
|
request_dir = pair/'writer-request-0001'
|
|
request_dir.mkdir(parents=True)
|
|
request = request_dir/'request.json'
|
|
request.write_text(json.dumps(dict(sequence=1, target_pid=123)))
|
|
raw = request_dir/'raw.jpg'
|
|
raw.write_bytes(b'\xff\xd8unit-only\xff\xd9')
|
|
shot = dict(path=str(raw.relative_to(root)), raw_sha256=check.sha(raw), tool_call_id='unit-shot-call')
|
|
receipt = request_dir/'cua.json'
|
|
receipt.write_text(json.dumps(dict(screenshots=[shot])))
|
|
ack = request_dir/'ack.json'
|
|
ack.write_text(json.dumps(dict(request_sha256=check.sha(request), input_receipt=str(receipt.relative_to(root)))))
|
|
(pair/'writer-requests.json').write_text(json.dumps(dict(requests=[str(ack.relative_to(root))])))
|
|
def convert(raw_path, png_path, cua, receipt_path):
|
|
self.assertEqual(cua, shot)
|
|
self.assertEqual(Path(raw_path).read_bytes(), b'\xff\xd8unit-only\xff\xd9')
|
|
Path(png_path).write_bytes(self.png())
|
|
Path(receipt_path).write_text('{}')
|
|
with mock.patch.object(runner, 'module', return_value=check), \
|
|
mock.patch.object(check, 'image_kind', return_value=('jpeg', 2, 2)), \
|
|
mock.patch.object(runner, 'convert_screenshot', side_effect=convert) as converter:
|
|
output = runner.collect_screenshots(root, pair, 'writer')
|
|
converter.assert_called_once()
|
|
rows = json.loads((root/output).read_text())
|
|
self.assertEqual(rows[0]['tool_call_id'], shot['tool_call_id'])
|
|
self.assertEqual(rows[0]['raw_sha256'], check.sha(raw))
|
|
self.assertEqual(rows[0]['png_sha256'], check.sha(root/rows[0]['png_path']))
|
|
self.assertTrue(rows[0]['conversion'])
|
|
|
|
@staticmethod
|
|
def png(mode=0, channels=3):
|
|
def chunk(kind, body):
|
|
return struct.pack('>I', len(body)) + kind + body + struct.pack('>I', zlib.crc32(kind + body))
|
|
rows = [bytes([10, 20, 30, 40][:channels] * 2),
|
|
bytes([50, 60, 70, 80][:channels] * 2)]
|
|
filtered = bytearray()
|
|
prior = bytes(2 * channels)
|
|
for row in rows:
|
|
filtered.append(mode)
|
|
for x, value in enumerate(row):
|
|
left, up, corner = (row[x-channels] if x >= channels else 0,
|
|
prior[x], prior[x-channels] if x >= channels else 0)
|
|
p = left + up - corner
|
|
distances = [abs(p-left), abs(p-up), abs(p-corner)]
|
|
predictors = [0, left, up, (left+up)//2,
|
|
[left, up, corner][distances.index(min(distances))]]
|
|
filtered.append((value-predictors[mode]) % 256)
|
|
prior = row
|
|
header = struct.pack('>IIBBBBB', 2, 2, 8, 2 if channels == 3 else 6, 0, 0, 0)
|
|
return (b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', header)
|
|
+ chunk(b'IDAT', zlib.compress(filtered)) + chunk(b'IEND', b''))
|
|
|
|
def test_all_png_filters_rgb_and_rgba(self):
|
|
for mode in range(5):
|
|
for channels in (3, 4):
|
|
with self.subTest(mode=mode, channels=channels):
|
|
w, h, rows = check.png_pixels(self.png(mode, channels))
|
|
self.assertEqual((w, h), (2, 2))
|
|
self.assertEqual(rows[0][0], tuple([10, 20, 30, 40][:channels]))
|
|
self.assertEqual(rows[1][1], tuple([50, 60, 70, 80][:channels]))
|
|
|
|
def test_crc_and_trailing_bytes_rejected(self):
|
|
data = bytearray(self.png())
|
|
data[29] ^= 1
|
|
with self.assertRaisesRegex(ValueError, 'CRC mismatch'):
|
|
check.png_pixels(bytes(data))
|
|
with self.assertRaisesRegex(ValueError, 'invalid IEND'):
|
|
check.png_pixels(self.png() + b'extra')
|
|
|
|
def conversion(self):
|
|
raw, png = b'\xff\xd8synthetic-unit-test\xff\xd9', self.png()
|
|
digest = lambda b: hashlib.sha256(b).hexdigest()
|
|
rec = dict(raw_path='/raw.png', raw_sha256=digest(raw), raw_kind='jpeg',
|
|
tool_call_id='unit-test-call', converter='/usr/bin/sips', converter_sha256='a'*64,
|
|
argv=['/usr/bin/sips', '-s', 'format', 'png', '/raw.png', '--out', '/decoded.png'],
|
|
exit_code=0, stdout_sha256=digest(b''), stderr_sha256=digest(b''),
|
|
png_path='/decoded.png', png_sha256=digest(png), width=2, height=2)
|
|
cua = dict(raw_sha256=digest(raw), tool_call_id='unit-test-call')
|
|
return rec, cua, {'/raw.png': raw, '/decoded.png': png}
|
|
|
|
def test_conversion_chain_and_specific_forgery_deficits(self):
|
|
rec, cua, files = self.conversion()
|
|
helper = mock.Mock()
|
|
helper.jpeg_dimensions.return_value = (2, 2)
|
|
with mock.patch.object(Path, 'read_bytes', lambda p: files[str(p)]), \
|
|
mock.patch.object(check, 'module', return_value=helper):
|
|
self.assertEqual(check.inspect_conversion(rec, cua, 'a'*64)[:2], (2, 2))
|
|
for key, value, deficit in [('converter_sha256', 'b'*64, 'converter SHA mismatch'),
|
|
('argv', [], 'converter argv mismatch'),
|
|
('raw_sha256', 'b'*64, 'CUA raw hash/call mismatch'),
|
|
('width', 3, 'conversion dimensions mismatch')]:
|
|
with self.subTest(key=key):
|
|
bad = dict(rec, **{key: value})
|
|
with self.assertRaisesRegex(ValueError, deficit):
|
|
check.inspect_conversion(bad, cua, 'a'*64)
|
|
with self.assertRaisesRegex(ValueError, 'JPEG screenshot requires conversion record'):
|
|
check.screenshot_pixels('/raw.png')
|
|
|
|
|
|
class RestAuditTests(unittest.TestCase):
|
|
def fixture(self):
|
|
member = dict(staff_id='s', state='OFF_DUTY', health=100, current_task_id='', current_cell=[0, 0, 0])
|
|
facilities = [dict(instance_id=fid, definition_id='staff_waiting_room', origin=[0, 0, 0],
|
|
occupied_cells=[], state='OPEN', suspension_reason='') for fid in ('a', 'b')]
|
|
runtime = dict(staff=dict(staff=[member]), facilities=dict(instances=facilities),
|
|
defense=dict(raids=[]), events=dict(actor_locks={}), elevators=dict(trips=[]),
|
|
path=dict(cell_traffic=[dict(cell=[x, 0, 0]) for x in (-1, 0, 1)]),
|
|
world=dict(bounds=dict(min_x=-1, max_x=1, min_y=0, max_y=0), occupancies=[]))
|
|
routes = [dict(cell=[x, 0, 0], path=[[0, 0, 0], [x, 0, 0]] if x else [[0, 0, 0]],
|
|
edges=[dict(**{'from': [0, 0, 0], 'to': [x, 0, 0]}, total_cost=0)] if x else [],
|
|
calculate_path_cost=0) for x in (-1, 0, 1)]
|
|
observed = []
|
|
for f in facilities:
|
|
observed.append({k: f[k] for k in ('instance_id', 'definition_id', 'origin', 'occupied_cells')})
|
|
observed[-1].update(operation_state='OPERATING', probes=[dict(cell=[x, 0, 0], has_cell=True) for x in (0, -1, 1)],
|
|
ordered=[[x, 0, 0] for x in (-1, 0, 1)], routes=copy.deepcopy(routes))
|
|
selected = dict(reachable=True, resting=True, cell=[-1, 0, 0], facility_id='a', label='待機室で休憩中')
|
|
audit = dict(path_service_sha256=check.sha(check.ROOT/'scripts/world/path_service.gd'),
|
|
staff=[dict(member, defense_memberships=[], actor_lock=False, in_transit=False,
|
|
facilities=observed, comparison_only=selected)])
|
|
report = dict(resting_staff={'s': 'a'}, actor_drawings=[], layer=0, carried_patients={}, rooms={},
|
|
viewport=[1440, 810], zoom=1, pan=[0, 0])
|
|
return dict(systems=dict(runtime=runtime)), report, audit
|
|
|
|
def test_selection_ties_and_nonmutation(self):
|
|
state, report, audit = self.fixture()
|
|
before = copy.deepcopy((state, report, audit))
|
|
self.assertEqual(check.compare_rest(state, report, audit)['s']['cell'], [-1, 0, 0])
|
|
self.assertEqual((state, report, audit), before)
|
|
|
|
def test_rest_forgeries(self):
|
|
mutations = {
|
|
'selection': lambda a: a['staff'][0]['comparison_only'].update(facility_id='b'),
|
|
'tie': lambda a: a['staff'][0]['comparison_only'].update(cell=[0, 0, 0]),
|
|
'lock': lambda a: a['staff'][0].update(actor_lock=True),
|
|
'transit': lambda a: a['staff'][0].update(in_transit=True),
|
|
'label': lambda a: a['staff'][0]['comparison_only'].update(label='宿舎で休憩中'),
|
|
'cost': lambda a: a['staff'][0]['facilities'][0]['routes'][0]['edges'][0].update(total_cost=9),
|
|
'facility_order': lambda a: a['staff'][0]['facilities'].reverse(),
|
|
'candidate_order': lambda a: a['staff'][0]['facilities'][0]['ordered'].reverse(),
|
|
'empty_edge': lambda a: a['staff'][0]['facilities'][0]['routes'][0]['edges'][0].clear(),
|
|
}
|
|
for name, mutate in mutations.items():
|
|
with self.subTest(name=name):
|
|
state, report, audit = self.fixture()
|
|
mutate(audit)
|
|
with self.assertRaises(ValueError):
|
|
check.compare_rest(state, report, audit)
|
|
|
|
def test_ineligible_and_transit_recomputed(self):
|
|
state, report, audit = self.fixture()
|
|
state['systems']['runtime']['events']['actor_locks']['s'] = 'event'
|
|
audit['staff'][0].update(actor_lock=True, comparison_only={})
|
|
report['resting_staff'] = {}
|
|
self.assertEqual(check.compare_rest(state, report, audit), {'s': {}})
|
|
state, report, audit = self.fixture()
|
|
state['systems']['runtime']['elevators']['trips'] = [dict(actor_id='staff:s')]
|
|
audit['staff'][0]['in_transit'] = True
|
|
audit['staff'][0]['comparison_only'].update(resting=False, label='休憩先へ移動中')
|
|
report['resting_staff'] = {}
|
|
self.assertFalse(check.compare_rest(state, report, audit)['s']['resting'])
|
|
|
|
|
|
class InfrastructureTests(unittest.TestCase):
|
|
def test_matrix_requires_each_condition_and_each_category(self):
|
|
executed = {c: {k: set(v) for k, v in categories.items()} for c, categories in check.REQUIRED_CHECKS.items()}
|
|
self.assertTrue(all(r['status'] == 'passed' for r in check.condition_matrix(executed)))
|
|
for condition in check.CONDITIONS:
|
|
for category in check.CATEGORIES:
|
|
with self.subTest(condition=condition, category=category):
|
|
broken = copy.deepcopy(executed)
|
|
broken[condition][category].clear()
|
|
matrix = check.condition_matrix(broken)
|
|
self.assertEqual(next(r for r in matrix if r['condition'] == condition)['status'], 'pending')
|
|
self.assertIn(category, check.BLOCKERS(broken)[condition])
|
|
|
|
def test_distinct_pid_ack_and_formal_hash_negatives(self):
|
|
members = {name: 'a'*64 for name in ('world.bin', 'manifest.json', 'thumbnail.png', 'journal.log')}
|
|
writer = dict(actual_pid=1)
|
|
reader = dict(actual_pid=2, formal_input={k: dict(sha256=v) for k, v in members.items()})
|
|
saved = dict(pid=1, members=members)
|
|
records = [dict(request=dict(action='load_menu', target_pid=2, sequence=1, requested_ticks_usec=10, requested_unix_ns=90),
|
|
ack=dict(target_pid=2, sequence=1, request_sha256='b'*64, timestamp_ns=110),
|
|
observed=dict(pid=2, sequence=1, request_sha256='b'*64, ack_observed_ticks_usec=20, ack_observed_unix_ns=120, status='手動セーブを読み込みました。'),
|
|
cua=dict(target_pid=2, method='native_os', tool_call_id='actual-call', timestamp_ns=100),
|
|
request_sha256='b'*64)]
|
|
check.inspect_resume(writer, reader, saved, records, members)
|
|
for change in ('pid', 'ack', 'time', 'hash', 'id'):
|
|
with self.subTest(change=change):
|
|
r, q = copy.deepcopy(reader), copy.deepcopy(records)
|
|
if change == 'pid': r['actual_pid'] = 1
|
|
if change == 'ack': q.clear()
|
|
if change == 'time': q[0]['observed']['ack_observed_ticks_usec'] = 9
|
|
if change == 'hash': r['formal_input']['world.bin']['sha256'] = 'c'*64
|
|
if change == 'id': q[0]['ack']['sequence'] = 2
|
|
with self.assertRaises(ValueError): check.inspect_resume(writer, r, saved, q, members)
|
|
|
|
def test_interpolation_history_tampering(self):
|
|
state = dict(systems=dict(runtime=dict(guests=dict(guests=[]), staff=dict(staff=[]),
|
|
elevators=dict(shafts=[], trips=[], current_minute=0))))
|
|
report = dict(animation_time=0.0, paused=True, layer=0, zoom=1, pan=[0,0], viewport=[100,100],
|
|
actor_tracks={}, elevator_tracks={}, actor_drawings=[], cabins=[])
|
|
audit = dict(frames=[dict(frame=1, delta=.01, scaled_delta=0, display=copy.deepcopy(report)),
|
|
dict(frame=2, delta=.01, scaled_delta=0, display=copy.deepcopy(report))])
|
|
check.compare_motion(state, report, audit)
|
|
for change in ('delta', 'frame', 'decreasing_frame', 'no_frames', 'one_frame', 'display', 'report_binding'):
|
|
with self.subTest(change=change):
|
|
bad = copy.deepcopy(audit)
|
|
if change == 'delta': bad['frames'][1]['scaled_delta'] = 1
|
|
if change == 'frame': bad['frames'][1]['frame'] = 1
|
|
if change == 'decreasing_frame': bad['frames'][1]['frame'] = 0
|
|
if change == 'no_frames': bad['frames'].clear()
|
|
if change == 'one_frame': bad['frames'].pop()
|
|
if change == 'report_binding': bad['frames'][1]['display']['zoom'] = 2
|
|
if change == 'display': bad['frames'][1]['display']['animation_time'] = 1
|
|
with self.assertRaises(ValueError): check.compare_motion(state, report, bad)
|
|
|
|
def test_verify_only_missing_index_is_a_concrete_deficit(self):
|
|
with mock.patch.dict(check.os.environ, {'BUILD_OUTPUT_DIR': '/missing-p23-proof'}, clear=True):
|
|
with contextlib.redirect_stdout(io.StringIO()) as output:
|
|
self.assertEqual(check.main(['--verify-only']), 1)
|
|
result = json.loads(output.getvalue())
|
|
self.assertEqual(result['integrity'], 'fail')
|
|
self.assertEqual(result['acceptance'], 'pending')
|
|
self.assertIn('index.json', result['deficits'][0])
|
|
self.assertNotIn('blocked', result['deficits'][0])
|
|
|
|
def test_verify_only_requires_build_output(self):
|
|
with mock.patch.dict(check.os.environ, {}, clear=True):
|
|
with contextlib.redirect_stderr(io.StringIO()) as output:
|
|
self.assertEqual(check.main(['--verify-only']), 2)
|
|
self.assertIn('BUILD_OUTPUT_DIR is required', output.getvalue())
|
|
|
|
def test_normative_plan_change_invalidates_but_progress_does_not(self):
|
|
root = check.ROOT
|
|
text = (root / check.PLAN).read_text()
|
|
original = check.input_map(root)
|
|
progress = text.splitlines(keepends=True)
|
|
progress[2] = 'Progress-only test mutation\n'
|
|
with mock.patch.object(Path, 'read_text', return_value=''.join(progress)):
|
|
self.assertEqual(check.input_map(root), original)
|
|
changed = text.replace('同需要の動線改善を観察できる。', 'altered normative acceptance')
|
|
self.assertNotEqual(changed, text)
|
|
with mock.patch.object(Path, 'read_text', return_value=changed):
|
|
self.assertNotEqual(check.input_map(root), original)
|
|
self.assertNotIn(check.PLAN, original)
|
|
self.assertIn('full_plan_sha256', check.input_provenance(root))
|
|
for path in (root / 'tests/fixtures/p23-03').rglob('*'):
|
|
if path.is_file():
|
|
self.assertIn(path.relative_to(root).as_posix(), original)
|
|
|
|
def test_os_process_guards_reject_engine_profile_and_source_drift(self):
|
|
source = {'source.gd': 'a' * 64}
|
|
receipt = dict(engine_ended=True, passed=True, exit_code=0, error=None,
|
|
guard_errors=[], cleanup_errors=[], default_before={}, default_after={},
|
|
stage_before={}, stage_after={}, source_map=source,
|
|
source_after={'source.gd': {'sha256': 'a' * 64}},
|
|
owned_cleanup=[{'engine_ended': True}], ordinary_owner_proof={'present': True})
|
|
receipt.update(spec_sha256=check.SPEC_SHA, inputs={'spec': 'a'*64}, inputs_after={'spec': 'a'*64},
|
|
environment={'P23_LAYOUT': 'A'}, environment_after={'P23_LAYOUT': 'A'}, actual_pid=123,
|
|
profile='/private/test', profile_name='test', profile_before={'existed': False, 'files': {}}, profile_after={})
|
|
for field in ('engine_sha256', 'settings_sha256', 'environment_sha256'):
|
|
receipt[field] = receipt[field+'_after'] = 'a'*64
|
|
check.inspect_os_receipt(receipt, source)
|
|
for field, value in [('engine_ended', False), ('default_after', {'changed': True}),
|
|
('source_after', {'source.gd': {'sha256': 'b' * 64}}),
|
|
('stage_after', {'changed': True}), ('owned_cleanup', []),
|
|
('environment_after', {}), ('profile_before', {}),
|
|
('engine_sha256_after', 'b'*64), ('spec_sha256', 'b'*64)]:
|
|
with self.subTest(field=field):
|
|
altered = copy.deepcopy(receipt)
|
|
altered[field] = value
|
|
with self.assertRaises(ValueError):
|
|
check.inspect_os_receipt(altered, source)
|
|
|
|
|
|
class SemanticTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.guest = dict(guest_id='g', state='QUEUING', current_cell=[1, 0, 0],
|
|
target_facility_id='f', current_service_id='', queue_request_id='q')
|
|
self.staff = dict(staff_id='s', state='WORKING', current_cell=[2, 0, 1],
|
|
current_task_id='t', assigned_facility_id='f', role_id='porter',
|
|
carried_inventory_id='cargo')
|
|
self.task = dict(task_id='t', state='WORKING', task_type='delivery',
|
|
assigned_staff_id='s', target_entity_id='request', rescue_facility_id='')
|
|
self.snapshot = {'systems': {'runtime': {'guests': {'guests': [self.guest]},
|
|
'staff': {'staff': [self.staff], 'tasks': [self.task]}}}}
|
|
self.report = dict(guests=[dict(id='g', state='QUEUING', cell=[1, 0, 0], target='f', service='', queue='q')],
|
|
staff=[dict(id='s', state='WORKING', cell=[2, 0, 1], task='t', facility='f',
|
|
role='porter', carried_inventory_id='cargo')],
|
|
tasks=[dict(id='t', state='WORKING', type='delivery', staff='s',
|
|
target='request', rescue_facility='')])
|
|
|
|
def test_real_schema_projection(self):
|
|
self.assertEqual(check.compare_actor_rows(self.snapshot, self.report), ['guests', 'staff', 'tasks'])
|
|
|
|
def test_changed_state_position_target_task_and_goods_are_rejected(self):
|
|
for category, field, replacement in [('guests', 'cell', [8, 0, 0]), ('guests', 'target', 'other'),
|
|
('guests', 'state', 'MOVING'), ('staff', 'task', ''),
|
|
('staff', 'carried_inventory_id', ''), ('tasks', 'staff', 'other')]:
|
|
with self.subTest(category=category, field=field):
|
|
report = copy.deepcopy(self.report)
|
|
report[category][0][field] = replacement
|
|
with self.assertRaises(ValueError):
|
|
check.compare_actor_rows(self.snapshot, report)
|
|
|
|
def test_missing_extra_and_boolean_only_rows_are_rejected(self):
|
|
for bad in [[], self.report['guests'] * 2, [True]]:
|
|
report = copy.deepcopy(self.report)
|
|
report['guests'] = bad
|
|
with self.assertRaises(ValueError):
|
|
check.compare_actor_rows(self.snapshot, report)
|
|
|
|
def test_unknown_enum_and_duplicate_id_are_rejected(self):
|
|
self.guest['state'] = 'FICTIONAL'
|
|
with self.assertRaises(ValueError):
|
|
check.compare_actor_rows(self.snapshot, self.report)
|
|
self.guest['state'] = 'QUEUING'
|
|
self.snapshot['systems']['runtime']['guests']['guests'].append(copy.deepcopy(self.guest))
|
|
with self.assertRaises(ValueError):
|
|
check.compare_actor_rows(self.snapshot, self.report)
|
|
|
|
def test_typed_rectangle_and_duplicate_vector_key(self):
|
|
self.assertEqual(check.decode(dict(type='Rect2', text='Rect2(1, 2, 16, 24)')), [1, 2, 16, 24])
|
|
with self.assertRaises(ValueError):
|
|
check.decode(dict(type='Rect2', text='Rect2(1, nan, 16, 24)'))
|
|
key = dict(type='Vector3i', text='Vector3i(1, 2, 0)')
|
|
value = dict(type='int', text='1')
|
|
with self.assertRaises(ValueError):
|
|
check.decode(dict(type='Dictionary', entries=[[key, value], [key, value]]))
|
|
|
|
def test_immutable_typed_input_is_not_modified(self):
|
|
before = copy.deepcopy(self.snapshot)
|
|
check.compare_actor_rows(self.snapshot, self.report)
|
|
self.assertEqual(before, self.snapshot)
|
|
|
|
|
|
class PackingTests(unittest.TestCase):
|
|
def test_scale_trial_rebuilds_vertical_groups(self):
|
|
members = self.members(2)
|
|
for member, y in zip(members, (118, 144)):
|
|
member['row'].update(center=[108, y], at=[84, y-15])
|
|
member['bounds'] = [80, 74, 20, 130]
|
|
# At p=3 the vertical intervals intersect; at p=2 they separate.
|
|
# Keeping the stale component would incorrectly force both down to p=1.
|
|
rows, badges = check.fitted_actor_layout(members, 4, 96)
|
|
self.assertEqual(badges, [])
|
|
self.assertEqual([rows['guest/'+str(i)]['scale'] for i in range(2)], [2, 2])
|
|
self.assertEqual([rows['guest/'+str(i)]['at'] for i in range(2)], [[76, 120], [76, 146]])
|
|
check.validate_actor_nonoverlap(dict(actor_drawings=list(rows.values()), cabins=[]))
|
|
|
|
def members(self, count=4):
|
|
# R13 cell dimensions and the actual _actor_offset sequence; no transit.
|
|
offsets = [0, 16, -16, 8, -8, 24, -24]
|
|
return [dict(row=dict(key='guest/'+str(i), center=[108+offsets[i % 7], 118-4*(i % 2)],
|
|
at=[84+offsets[i % 7], 103-4*(i % 2)], scale=3,
|
|
mode='idle', frame=0, flip=False, state=3, cell=[1, 1, 0]),
|
|
bounds=[80, 74, 56, 88], transit=False) for i in range(count)]
|
|
|
|
def test_four_movers_fit_at_two_not_three(self):
|
|
rows, badges = check.fitted_actor_layout(self.members(), 4, 96)
|
|
self.assertEqual(badges, [])
|
|
self.assertEqual([rows['guest/'+str(i)]['scale'] for i in range(4)], [2]*4)
|
|
self.assertEqual([rows['guest/'+str(i)]['at'] for i in range(4)],
|
|
[[88, 120], [112, 116], [76, 120], [100, 116]])
|
|
check.validate_actor_nonoverlap(dict(actor_drawings=list(rows.values()), cabins=[]))
|
|
|
|
def test_cross_kind_group_and_passenger(self):
|
|
members = self.members()
|
|
members[1]['row']['key'] = 'staff/worker'
|
|
members[2]['row']['key'] = 'guest/passenger'
|
|
members[2]['row']['scale'] = 2
|
|
members[2]['row']['at'] = [76, 120]
|
|
rows, badges = check.fitted_actor_layout(members, 4, 96)
|
|
self.assertEqual(badges, [])
|
|
self.assertEqual(len(rows), 4)
|
|
check.validate_actor_nonoverlap(dict(actor_drawings=list(rows.values()), cabins=[]))
|
|
|
|
def test_saturation_keeps_all_identities(self):
|
|
rows, badges = check.fitted_actor_layout(self.members(12), 4, 96)
|
|
self.assertEqual(len(rows), 9)
|
|
self.assertEqual(len(badges), 1)
|
|
self.assertEqual(len(badges[0]['members']), 12)
|
|
self.assertEqual(badges[0]['text'], '+3')
|
|
self.assertEqual(set(badges[0]['drawn_keys']), set(rows))
|
|
self.assertTrue(all(r['scale'] == 1 for r in rows.values()))
|
|
check.validate_actor_nonoverlap(dict(actor_drawings=list(rows.values()), cabins=[]))
|
|
|
|
def test_each_actor_keeps_own_bounds(self):
|
|
members = self.members(2)
|
|
members[0]['bounds'] = [80, 74, 12, 88]
|
|
members[1]['bounds'] = [124, 74, 12, 88]
|
|
rows, badges = check.fitted_actor_layout(members, 4, 96)
|
|
self.assertEqual(badges, [])
|
|
self.assertEqual(check.actor_identity_core(rows['guest/0'])[0:3:2], [80, 12])
|
|
self.assertEqual(check.actor_identity_core(rows['guest/1'])[0:3:2], [124, 12])
|
|
|
|
def test_receiving_cafeteria_uses_r13_room_not_access_cell(self):
|
|
fixture = json.loads((check.ROOT/'tests/fixtures/p23-03/r13_service_region_gap.json').read_text())
|
|
guest = fixture['guests'][0]
|
|
rect = fixture['room_rects'][guest['target']]
|
|
anchor = check.room_slots(rect, 4, 'cafeteria')[0]
|
|
center = [anchor[0], anchor[1]+12-48]
|
|
member = dict(row=dict(key='guest/'+guest['id'], center=center,
|
|
at=[center[0]-24, center[1]-15], scale=3,
|
|
mode='eat', frame=0, flip=False, state=5, cell=guest['cell']),
|
|
bounds=[rect[0]+4, rect[1]+4, rect[2]-8, rect[3]-8], transit=False)
|
|
rows, badges = check.fitted_actor_layout([member], 4, 96)
|
|
self.assertFalse(badges)
|
|
core = check.actor_identity_core(next(iter(rows.values())))
|
|
self.assertGreaterEqual(core[0], 1031)
|
|
self.assertEqual(next(iter(rows.values()))['cell'], [5, 0, 0])
|
|
|
|
|
|
class SpatialTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.runtime = dict(world=dict(bounds=dict(min_x=0, max_x=1, min_y=0, max_y=0),
|
|
walkable_cells=[[0, 0, 0], [1, 0, 0]], occupancies=[]),
|
|
path=dict(cell_traffic=[dict(cell=[0, 0, 0], base_capacity=2,
|
|
current_density=3, dynamic_cost=0, temporary_block=False)]),
|
|
facilities=dict(instances=[], requests=[]), guests=dict(guests=[]),
|
|
staff=dict(staff=[], tasks=[]), logistics_requests=[],
|
|
elevators=dict(current_minute=0, shafts=[], trips=[]))
|
|
self.snapshot = dict(systems=dict(runtime=self.runtime))
|
|
self.report = dict(viewport=[184, 164], zoom=1, pan=[0, 0], layer=0,
|
|
traffic=[dict(cell=[0, 0, 0], rect=[78, 72, 12, 20],
|
|
band_rect=[79.44, 90.4, 9.12, 3.2],
|
|
traffic=dict(base_capacity=2, current_density=3,
|
|
dynamic_cost=0, temporary_block=False))],
|
|
queue_positions={}, queue_hidden={}, landing_badges=[], rooms={},
|
|
treated_patients={}, carried_patients={}, resting_staff={},
|
|
goods=dict(requests=[], deliveries=[]), actor_drawings=[], cabins=[],
|
|
observation_serial=1, overlay_consumed_serial=1,
|
|
overlay_passes=['traffic', 'actors'], overlay_child_indices=[0, 1, 2])
|
|
synthetic_repair28_fields(self.report)
|
|
|
|
def four_movers(self):
|
|
self.runtime['world'].update(bounds=dict(min_x=1, max_x=1, min_y=1, max_y=1), walkable_cells=[[1, 1, 0]])
|
|
self.report.update(viewport=[216, 236], paused=True, animation_time=0,
|
|
actor_tracks={}, boarded={}, actor_aggregations=[])
|
|
for i, (center, at) in enumerate(zip([[108,118], [124,114], [92,118], [116,114]],
|
|
[[88,120], [112,116], [76,120], [100,116]])):
|
|
key = 'guest/'+str(i)
|
|
self.runtime['guests']['guests'].append(dict(guest_id=str(i), current_cell=[1,1,0],
|
|
current_service_id='', target_facility_id='', state='MOVING'))
|
|
self.report['actor_tracks'][key] = dict(cell=[1,1,0], **{'from':[1,1,0]}, at=0, direction=1)
|
|
hashed = 5381
|
|
for char in key:
|
|
hashed = (hashed*33+ord(char)) & 0xffffffff
|
|
self.report['actor_drawings'].append(dict(key=key, center=center, at=at, scale=2,
|
|
cell=[1,1,0], mode='idle', frame=hashed%15, variant=hashed%15, flip=False,
|
|
staff=False, state=3, receiving=False, carrying=False, riding=False))
|
|
|
|
def test_four_movers_complete_geometry(self):
|
|
self.four_movers()
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def moving_four(self):
|
|
self.four_movers()
|
|
self.report.update(paused=False, animation_time=.21, badges=[])
|
|
for row in self.report['actor_drawings']:
|
|
self.report['actor_tracks'][row['key']]['from'] = [0, 1, 0]
|
|
row['center'][0] -= 32
|
|
row.update(scale=3, mode='walk', frame=row['variant']+2)
|
|
row['at'] = [row['center'][0]-24, row['center'][1]-15]
|
|
|
|
def drawing_audit(self):
|
|
rows = []
|
|
for category, identifier, rect, source, text in check.drawing_rectangle_rows(self.report):
|
|
self.assertIsNone(text)
|
|
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
|
(check.ROOT/source).read_text())))
|
|
rows.append(dict(category=category, id=identifier, rect=rect,
|
|
style_source=source, style_colors=colors))
|
|
return dict(rows=rows)
|
|
|
|
def test_transit_raw_state_in_full_and_standalone_paths(self):
|
|
self.moving_four()
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
check.validate_drawing_rows(self.report, self.drawing_audit(), state=self.snapshot)
|
|
# Without authoritative context the overlapping moving cores fail closed.
|
|
with self.assertRaisesRegex(ValueError, 'core overlap'):
|
|
check.validate_drawing_rows(self.report, self.drawing_audit())
|
|
|
|
def test_transit_cannot_be_claimed_by_flags_or_invalid_tracks(self):
|
|
self.moving_four()
|
|
mutations = {
|
|
'paused': lambda r: r.update(paused=True),
|
|
'expired': lambda r: r.update(animation_time=.42),
|
|
'nonboolean_pause': lambda r: r.update(paused=0),
|
|
'future': lambda r: r['actor_tracks']['guest/0'].update(at=1),
|
|
'nonfinite': lambda r: r.update(animation_time=float('nan')),
|
|
'wrong_endpoint': lambda r: r['actor_tracks']['guest/0'].update(cell=[0, 1, 0]),
|
|
'teleport_origin': lambda r: r['actor_tracks']['guest/0'].update({'from': [-9, 1, 0]}),
|
|
}
|
|
for name, mutate in mutations.items():
|
|
report = copy.deepcopy(self.report)
|
|
mutate(report)
|
|
for row in report['actor_drawings']:
|
|
row.update(in_transit=True, region='transit')
|
|
with self.subTest(name=name), self.assertRaises(ValueError):
|
|
check.validate_drawing_rows(report, self.drawing_audit(), state=self.snapshot)
|
|
|
|
def test_staff_and_guests_share_complete_geometry_front_and_back(self):
|
|
self.four_movers()
|
|
self.runtime['guests']['guests'].pop()
|
|
self.runtime['staff']['staff'] = [dict(staff_id='3', current_cell=[1, 1, 0],
|
|
state='AVAILABLE', assigned_facility_id='', carried_inventory_id='', role_id='guard')]
|
|
self.report['actor_tracks']['staff/3'] = self.report['actor_tracks'].pop('guest/3')
|
|
row = self.report['actor_drawings'][-1]
|
|
hashed = 5381
|
|
for char in 'staff/3':
|
|
hashed = (hashed*33+ord(char)) & 0xffffffff
|
|
row.update(key='staff/3', staff=True, state=2, variant=hashed%15, frame=hashed%15)
|
|
for layer in (0, 1):
|
|
self.report['layer'] = layer
|
|
for member in self.runtime['guests']['guests'] + self.runtime['staff']['staff']:
|
|
member['current_cell'][2] = layer
|
|
for track in self.report['actor_tracks'].values():
|
|
track['cell'][2] = layer
|
|
track['from'][2] = layer
|
|
for drawing in self.report['actor_drawings']:
|
|
drawing['cell'][2] = layer
|
|
with self.subTest(layer=layer):
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_spoofed_region_does_not_allow_core_outside_own_cell(self):
|
|
self.four_movers()
|
|
self.report['actor_drawings'][0].update(region='facility', in_transit=True, at=[500, 120])
|
|
with self.assertRaises(ValueError):
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_aggregation_badge_is_required_in_drawing_audit(self):
|
|
self.four_movers()
|
|
self.report['badges'] = []
|
|
audit = self.drawing_audit()
|
|
self.report['actor_aggregations'] = [dict(at=[80, 74], text='+1',
|
|
members=[], drawn_keys=[])]
|
|
# This low-level test covers badge provenance only. Full geometry already
|
|
# rejects the unnecessary aggregate in test_fitted_geometry_rejects_tampering.
|
|
with self.assertRaisesRegex(ValueError, 'offline rectangle coverage'):
|
|
check.validate_drawing_rows(self.report, audit)
|
|
|
|
def test_low_world_pixel_complete_geometry_keeps_fractional_base(self):
|
|
self.four_movers()
|
|
self.runtime['guests']['guests'] = self.runtime['guests']['guests'][:1]
|
|
self.report.update(viewport=[156, 146], actor_drawings=self.report['actor_drawings'][:1])
|
|
self.report['actor_drawings'][0].update(center=[78, 73], at=[77, 72], scale=.1875)
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_saturated_complete_geometry_keeps_every_identity(self):
|
|
self.four_movers()
|
|
prototype = copy.deepcopy(self.report['actor_drawings'][0])
|
|
self.runtime['guests']['guests'] = []
|
|
self.report['actor_drawings'] = []
|
|
# 10 upright cores need 60px at p=1, but the interval is only 56px.
|
|
# The first nine in preferred-x/key order occupy x82..136; guest/5
|
|
# cannot be retained. This expected layout is independent of the solver.
|
|
order = [6, 2, 9, 4, 0, 7, 3, 1, 8, 5]
|
|
identities = {}
|
|
for i in range(10):
|
|
key = 'guest/'+str(i)
|
|
cell = [1, 1, 0]
|
|
self.runtime['guests']['guests'].append(dict(guest_id=str(i), current_cell=cell,
|
|
current_service_id='', target_facility_id='', state='MOVING'))
|
|
self.report['actor_tracks'][key] = dict(cell=cell, **{'from': cell}, at=0, direction=1)
|
|
identities[i] = dict(key=key, id=str(i), cell=cell, state=3, mode='idle')
|
|
if i == 5:
|
|
continue
|
|
hashed = 5381
|
|
for char in key:
|
|
hashed = (hashed*33+ord(char)) & 0xffffffff
|
|
center = [108+[0,16,-16,8,-8,24,-24][i%7], 118-4*(i%2)]
|
|
self.report['actor_drawings'].append(dict(prototype, key=key, center=center,
|
|
at=[76+6*order.index(i), center[1]+19], scale=1, frame=hashed%15, variant=hashed%15))
|
|
self.report['actor_aggregations'] = [dict(members=[identities[i] for i in order],
|
|
drawn_keys=['guest/'+str(i) for i in order[:-1]], at=[80,74], text='+1')]
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
self.report['actor_aggregations'][0]['members'].pop()
|
|
with self.assertRaisesRegex(ValueError, 'aggregation/no-fit'):
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_fitted_geometry_rejects_tampering(self):
|
|
self.four_movers()
|
|
for field, value in [('cell', [0,1,0]), ('state', 4), ('scale', 3), ('at', [999,120])]:
|
|
report = copy.deepcopy(self.report)
|
|
report['actor_drawings'][0][field] = value
|
|
with self.subTest(field=field), self.assertRaises(ValueError):
|
|
check.compare_actor_geometry(self.snapshot, report)
|
|
for mutation in ('missing', 'duplicate', 'aggregate'):
|
|
report = copy.deepcopy(self.report)
|
|
if mutation == 'missing':
|
|
report['actor_drawings'].pop()
|
|
elif mutation == 'duplicate':
|
|
report['actor_drawings'].append(copy.deepcopy(report['actor_drawings'][0]))
|
|
else:
|
|
report['actor_aggregations'] = [dict(drawn_keys=[], text='+4')]
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
|
check.compare_actor_geometry(self.snapshot, report)
|
|
|
|
def test_density_and_pan_are_independently_recomputed(self):
|
|
check.compare_traffic(self.snapshot, self.report)
|
|
for key, value in [('current_density', 0), ('base_capacity', 9)]:
|
|
changed = copy.deepcopy(self.report)
|
|
changed['traffic'][0]['traffic'][key] = value
|
|
with self.assertRaises(ValueError):
|
|
check.compare_traffic(self.snapshot, changed)
|
|
self.report['pan'][0] = 20
|
|
with self.assertRaises(ValueError):
|
|
check.compare_traffic(self.snapshot, self.report)
|
|
|
|
def test_missing_queue_overflow_rejected(self):
|
|
saved = json.loads((check.ROOT / 'tests/fixtures/p23-03/queued_elevator.json').read_text())['saved']
|
|
self.runtime['elevators'] = saved
|
|
saved['trips'] = [dict(copy.deepcopy(saved['trips'][0]), actor_id='guest:g'+str(i),
|
|
request_id='trip'+str(i), enqueued_at=i) for i in range(3)]
|
|
self.report.update(shaft_footprint=[[0,0,0],[1,0,0],[0,1,0],[1,1,0]], queue_positions={},
|
|
queue_hidden={'guest/g0': True, 'guest/g1': True, 'guest/g2': True}, landing_badges=[dict(at=[-4, 54],
|
|
text='待ち3人(他3人)', cell=[0, 0, 0],
|
|
members=['guest/g0', 'guest/g1', 'guest/g2'], visible_count=0, overflow=3)])
|
|
check.compare_queues(self.snapshot, self.report)
|
|
self.report['landing_badges'][0]['overflow'] = 0
|
|
with self.assertRaises(ValueError):
|
|
check.compare_queues(self.snapshot, self.report)
|
|
|
|
def test_fictional_goods_and_rooms_rejected(self):
|
|
check.compare_goods(self.snapshot, self.report)
|
|
check.compare_room_services(self.snapshot, self.report)
|
|
self.report['goods']['requests'].append(dict(stock=100))
|
|
with self.assertRaises(ValueError):
|
|
check.compare_goods(self.snapshot, self.report)
|
|
self.report['rooms']['invented'] = dict(count=0)
|
|
with self.assertRaises(ValueError):
|
|
check.compare_room_services(self.snapshot, self.report)
|
|
|
|
def test_room_reason_and_rescue_occupancy(self):
|
|
room = dict(instance_id='clinic', origin=[0, 0, 0], definition_id='infirmary',
|
|
state='NO_POWER', state_reasons=['NO_POWER'], suspension_reason='',
|
|
customer_capacity=2, active_staff_ids=[], active_services=[], occupied_cells=[])
|
|
self.runtime['facilities']['instances'] = [room]
|
|
self.runtime['staff']['tasks'] = [dict(task_id='rescue', task_type='rescue_transport',
|
|
state='WORKING', assigned_staff_id='carrier', target_entity_id='patient', rescue_facility_id='clinic')]
|
|
self.report['treated_patients'] = {'patient': 'clinic'}
|
|
self.report['rooms'] = {'clinic': dict(state=2, state_name='NO_POWER', reasons=['NO_POWER'],
|
|
operation=0, people=['patient'], count=1, limit=2, staff=[], suspension='')}
|
|
check.compare_room_services(self.snapshot, self.report)
|
|
for field, wrong in [('state', 6), ('operation', 1), ('people', []), ('count', 0)]:
|
|
changed = copy.deepcopy(self.report)
|
|
changed['rooms']['clinic'][field] = wrong
|
|
with self.assertRaises(ValueError):
|
|
check.compare_room_services(self.snapshot, changed)
|
|
|
|
def test_cabin_geometry_and_phase(self):
|
|
shaft = dict(config=dict(shaft_id='lift', stops=[[0, 0, 0]], width=2, capacity=4),
|
|
current_floor=0, phase='IDLE', operational=True, boarded=[])
|
|
self.runtime['elevators']['shafts'] = [shaft]
|
|
self.report.update(paused=True, animation_time=0, elevator_tracks={'lift': dict(floor=0, at=0)},
|
|
cabins=[dict(shaft_id='lift', rect=[80, 72, 24, 19], pixel=1, operational=True,
|
|
doors_open=True, badge_at=[80, 58], badge='0/4人', passengers=[])])
|
|
synthetic_repair28_fields(self.report, self.snapshot)
|
|
check.compare_cabin_geometry(self.snapshot, self.report)
|
|
for field, value in [('rect', [80, 70, 24, 19]), ('doors_open', False), ('badge', '4/4人')]:
|
|
changed = copy.deepcopy(self.report)
|
|
changed['cabins'][0][field] = value
|
|
with self.assertRaises(ValueError):
|
|
check.compare_cabin_geometry(self.snapshot, changed)
|
|
|
|
|
|
class AnchoredGeometryTests(unittest.TestCase):
|
|
"""Whole state classification fixtures, with independently chosen draw rows."""
|
|
def setUp(self):
|
|
SpatialTests.setUp(self)
|
|
self.runtime['world'].update(bounds=dict(min_x=0, max_x=3, min_y=0, max_y=1),
|
|
walkable_cells=[[x, y, 0] for y in range(2) for x in range(4)])
|
|
self.report.update(viewport=[408, 332], paused=True, animation_time=0,
|
|
actor_tracks={}, boarded={}, actor_aggregations=[], badges=[], traffic=[])
|
|
self.runtime.update(defense=dict(raids=[]), events=dict(actor_locks={}))
|
|
|
|
def person(self, key, cell, state='MOVING', **fields):
|
|
kind, identifier = key.split('/')
|
|
if kind == 'guest':
|
|
person = dict(guest_id=identifier, current_cell=cell, state=state,
|
|
current_service_id='', target_facility_id='')
|
|
self.runtime['guests']['guests'].append(person)
|
|
else:
|
|
person = dict(staff_id=identifier, current_cell=cell, state=state, health=100,
|
|
assigned_facility_id='', current_task_id='', carried_inventory_id='', role_id='guard')
|
|
self.runtime['staff']['staff'].append(person)
|
|
person.update(fields)
|
|
self.report['actor_tracks'][key] = dict(cell=cell[:], **{'from': cell[:]}, at=0, direction=1)
|
|
return person
|
|
|
|
def drawing(self, key, center, mode='idle', scale=3, receiving=False, riding=False, at=None):
|
|
kind, identifier = key.split('/')
|
|
source = self.runtime['staff']['staff'] if kind == 'staff' else self.runtime['guests']['guests']
|
|
person = next(r for r in source if r[kind+'_id'] == identifier)
|
|
hashed = 5381
|
|
for char in key:
|
|
hashed = (hashed*33+ord(char)) & 0xffffffff
|
|
row = dict(key=key, center=center, at=at or [center[0]-8*scale, center[1]+36-17*scale],
|
|
scale=scale, mode=mode, frame=hashed%15, variant=hashed%15, flip=False,
|
|
staff=kind == 'staff', state=check.enum_names(check.ROOT,
|
|
'scripts/'+kind+'/'+kind+'_runtime.gd', 'State').index(person['state']),
|
|
cell=person['current_cell'][:], receiving=receiving, carrying=False, riding=riding)
|
|
if not riding:
|
|
self.report['actor_drawings'].append(row)
|
|
return row
|
|
|
|
def room(self, fid, definition, cells, users=(), workers=()):
|
|
facility = dict(instance_id=fid, definition_id=definition, origin=cells[0], occupied_cells=cells,
|
|
state='OPEN', state_reasons=[], suspension_reason='', customer_capacity=10,
|
|
active_services=[dict(guest_ids=list(users))] if users else [], active_staff_ids=list(workers), queue_entries=[])
|
|
self.runtime['facilities']['instances'].append(facility)
|
|
self.runtime['world']['occupancies'].append(dict(entity_id=fid, cells=cells))
|
|
self.report['rooms'][fid] = dict(people=list(users), count=len(users), staff=list(workers),
|
|
state=check.enum_names(check.ROOT, 'scripts/facility/facility_instance.gd', 'State').index('OPEN'),
|
|
state_name='OPEN', reasons=[], operation=1, limit=10, suspension='', definition_id=definition,
|
|
rect=check.Transform(self.runtime, self.report).room(cells))
|
|
return facility
|
|
|
|
def queue_work(self):
|
|
facility = self.room('work', 'cafeteria', [[x,y,0] for y in (0,1) for x in (2,3)], workers=['w'])
|
|
for key in ('a', 'b'):
|
|
self.person('guest/'+key, [1,1,0], 'QUEUING', current_service_id='service', target_facility_id='work')
|
|
# Queue takes precedence even over a nonempty current_service_id.
|
|
facility['active_services'] = [dict(guest_ids=['a','b'])]
|
|
self.report['rooms']['work'].update(people=['a','b'], count=2)
|
|
facility['queue_entries'] = ['request']
|
|
self.runtime['facilities']['requests'] = [dict(request_id='request', guest_ids=['a','b'])]
|
|
self.person('staff/w', [2,1,0], 'WORKING', assigned_facility_id='work')
|
|
self.report['queue_positions'] = {'guest/a':[172,118], 'guest/b':[108,118]}
|
|
self.report['landing_badges'] = [dict(at=[140,54], text='待ち2人', cell=[1,1,0],
|
|
members=['guest/a','guest/b'], visible_count=2, overflow=0)]
|
|
self.drawing('guest/a', [172,118], 'eat', receiving=True)
|
|
self.drawing('guest/b', [108,118], 'eat', receiving=True)
|
|
self.drawing('staff/w', [236,126], 'work')
|
|
|
|
def test_queue_lane_and_work_staff(self):
|
|
self.queue_work()
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_queue_precedence_order_and_category_forgeries(self):
|
|
self.queue_work()
|
|
for mutation in ('precedence', 'order', 'category', 'feet', 'pose'):
|
|
report = copy.deepcopy(self.report)
|
|
if mutation == 'precedence': report['actor_drawings'][0].update(center=[268,210], at=[244,195])
|
|
if mutation == 'order':
|
|
report['queue_positions']['guest/a'], report['queue_positions']['guest/b'] = report['queue_positions']['guest/b'], report['queue_positions']['guest/a']
|
|
if mutation == 'category': report['actor_drawings'][0].update(staff=True, region='work', in_transit=True)
|
|
if mutation == 'feet': report['actor_drawings'][0]['at'][1] -= 17
|
|
if mutation == 'pose': report['actor_drawings'][0]['mode'] = 'wait'
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
|
check.compare_actor_geometry(self.snapshot, report)
|
|
|
|
def test_receiving_r13_guest_cannot_be_forced_into_access_cell(self):
|
|
fixture = json.loads((check.ROOT/'tests/fixtures/p23-03/r13_service_region_gap.json').read_text())
|
|
guest = fixture['guests'][0]
|
|
self.runtime['world']['bounds'].update(max_x=9)
|
|
self.report.update(viewport=[792,332], pan=[567,318])
|
|
fid = guest['target']
|
|
self.room(fid, 'cafeteria', [[x,y,0] for y in (0,1) for x in range(6,10)], users=[guest['id']])
|
|
self.person('guest/'+guest['id'], guest['cell'], 'RECEIVING_SERVICE', current_service_id='service', target_facility_id=fid)
|
|
# Exact historical room x1027..1283; access cell x963..1027.
|
|
self.assertEqual(self.report['rooms'][fid]['rect'], fixture['room_rects'][fid])
|
|
row = self.drawing('guest/'+guest['id'], [1069+2/3,528], 'eat', receiving=True, at=[1046,513])
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
row.update(center=[995,532], at=[971,517], region='floor')
|
|
with self.assertRaises(ValueError): check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_carried_patient_and_treated_staff(self):
|
|
self.person('staff/carrier', [0,0,0], 'AVAILABLE')
|
|
self.person('staff/patient', [0,0,0], 'INCAPACITATED')
|
|
self.person('staff/treated', [2,0,0], 'INCAPACITATED')
|
|
self.room('clinic', 'infirmary', [[2,0,0]])
|
|
self.runtime['staff']['tasks'] = [dict(task_type='rescue_transport', state=phase,
|
|
assigned_staff_id='carrier', target_entity_id=patient, rescue_facility_id='clinic')
|
|
for patient, phase in [('patient','MOVING'), ('treated','WORKING')]]
|
|
self.report.update(carried_patients={'patient':'carrier'}, treated_patients={'treated':'clinic'})
|
|
self.report['rooms']['clinic'].update(people=['treated'], count=1)
|
|
self.drawing('staff/carrier', [108,214])
|
|
self.drawing('staff/patient', [108,166], 'collapsed')
|
|
row = self.drawing('staff/treated', [236,190], 'collapsed')
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
self.report['actor_drawings'][1]['center'][1] += 4
|
|
self.report['actor_drawings'][1]['at'][1] += 4
|
|
with self.assertRaises(ValueError): check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def cabin(self, count):
|
|
shaft = json.loads((check.ROOT/'tests/fixtures/p23-03/queued_elevator.json').read_text())['saved']['shafts'][0]
|
|
shaft.update(current_floor=0, phase='IDLE', boarded=['guest:p%02d'%i for i in range(count)])
|
|
# Synthetic saved configuration, not a production definition or capacity edit.
|
|
shaft['config']['capacity'] = max(4, count)
|
|
self.runtime['elevators']['shafts'] = [shaft]
|
|
sid = shaft['config']['shaft_id']
|
|
self.report['elevator_tracks'] = {sid: dict(floor=0, **{'from':0}, at=0)}
|
|
cabin = dict(shaft_id=sid, rect=[92,174,96,76], pixel=4, operational=True, doors_open=True,
|
|
badge_at=[92,160], badge=('満員 ' if count >= 4 else '')+f'{count}/{max(4,count)}人', passengers=[])
|
|
self.report['cabins'] = [cabin]
|
|
synthetic_repair28_fields(self.report, self.snapshot)
|
|
for i in range(count):
|
|
key = 'guest/p%02d'%i
|
|
self.person(key, [0,0,0])
|
|
self.report['boarded'][key] = True
|
|
cabin['passengers'].append(self.drawing(key, [116+32*(i%2),206-36*((i//2)%2)], scale=2, riding=True))
|
|
return cabin
|
|
|
|
def occupied_rest(self):
|
|
state, _, audit = RestAuditTests().fixture()
|
|
old = state['systems']['runtime']
|
|
self.runtime['path'] = old['path']
|
|
self.person('staff/s', [0,0,0], 'OFF_DUTY')
|
|
self.room('a', 'staff_waiting_room', [[0,0,0]])
|
|
self.room('b', 'staff_waiting_room', [[0,0,0]])
|
|
for facility in self.runtime['facilities']['instances']:
|
|
facility['occupied_cells'] = [] # Public selector falls back to origin.
|
|
self.report['resting_staff'] = {'s':'a'}
|
|
self.report['rooms']['a'].update(people=['s'], count=1)
|
|
self.drawing('staff/s', [108,198])
|
|
return audit
|
|
|
|
def test_compare_rest_with_actual_occupied_facility(self):
|
|
audit = self.occupied_rest()
|
|
self.assertTrue(check.compare_rest(self.snapshot, self.report, audit)['s']['resting'])
|
|
self.report['actor_drawings'][0]['at'][1] += 1
|
|
with self.assertRaises(ValueError): check.compare_rest(self.snapshot, self.report, audit)
|
|
|
|
def test_compare_rest_independently_proves_no_fit(self):
|
|
audit = self.occupied_rest()
|
|
rest = self.report['actor_drawings'].pop()
|
|
rest.update(scale=1, at=[100,217])
|
|
order = [6,2,9,4,0,7,3,1,8,5]
|
|
identities = {}
|
|
for i in range(10):
|
|
key = 'guest/'+str(i)
|
|
self.person(key, [0,0,0])
|
|
center = [108+[0,16,-16,8,-8,24,-24][i%7],214-4*(i%2)]
|
|
row = self.drawing(key, center, scale=1, at=[76+6*order.index(i),center[1]+19])
|
|
identities[i] = dict(key=key, id=str(i), state=3, cell=[0,0,0], mode='idle')
|
|
self.report['actor_drawings'] = [r for r in self.report['actor_drawings'] if r['key'] != 'guest/5']+[rest]
|
|
self.report['actor_aggregations'] = [dict(members=[identities[i] for i in order],
|
|
drawn_keys=['guest/'+str(i) for i in order[:9]], at=[80,170], text='+1')]
|
|
check.compare_rest(self.snapshot, self.report, audit)
|
|
stale = copy.deepcopy(self.report)
|
|
stale['actor_aggregations'][0]['members'].append(dict(key='staff/s', id='s', state=0, cell=[0,0,0], mode='idle'))
|
|
with self.assertRaises(ValueError): check.compare_rest(self.snapshot, stale, audit)
|
|
self.report['actor_aggregations'][0]['members'].pop()
|
|
with self.assertRaises(ValueError): check.compare_rest(self.snapshot, self.report, audit)
|
|
|
|
def test_compare_rest_with_queued_staff(self):
|
|
audit = self.occupied_rest()
|
|
self.person('staff/q', [0,0,0], 'OFF_DUTY')
|
|
saved = json.loads((check.ROOT/'tests/fixtures/p23-03/queued_elevator.json').read_text())['saved']
|
|
saved['trips'][0]['actor_id'] = 'staff:q'
|
|
self.runtime['elevators'] = saved
|
|
self.runtime['world']['walkable_cells'].append([-1,0,0])
|
|
self.report['queue_positions'] = {'staff/q':[44,214]}
|
|
self.report['landing_badges'] = [dict(at=[-4,150], text='待ち1人', cell=[0,0,0],
|
|
members=['staff/q'], visible_count=1, overflow=0)]
|
|
queued = copy.deepcopy(audit['staff'][0])
|
|
queued.update(staff_id='q', in_transit=True)
|
|
queued['comparison_only'].update(resting=False, label='休憩先へ移動中')
|
|
audit['staff'].append(queued)
|
|
self.drawing('staff/q', [44,214], 'wait')
|
|
synthetic_repair28_fields(self.report, self.snapshot)
|
|
result = check.compare_rest(self.snapshot, self.report, audit)
|
|
self.assertTrue(result['s']['resting'])
|
|
self.assertFalse(result['q']['resting'])
|
|
self.report['actor_drawings'][-1].update(center=[108,198], at=[84,183])
|
|
with self.assertRaises(ValueError): check.compare_rest(self.snapshot, self.report, audit)
|
|
|
|
def test_passenger_and_floor_actor_front_and_back(self):
|
|
cabin = self.cabin(1)
|
|
self.person('guest/floor', [2,0,0])
|
|
self.drawing('guest/floor', [236,214])
|
|
for layer in (0,1):
|
|
self.report['layer'] = layer
|
|
for guest in self.runtime['guests']['guests']: guest['current_cell'][2] = layer
|
|
for cell in self.runtime['elevators']['shafts'][0]['config']['stops']: cell[2] = layer
|
|
for row in self.report['actor_drawings']+cabin['passengers']: row['cell'][2] = layer
|
|
for track in self.report['actor_tracks'].values():
|
|
track['cell'][2] = track['from'][2] = layer
|
|
synthetic_repair28_fields(self.report, self.snapshot)
|
|
with self.subTest(layer=layer):
|
|
check.compare_cabin_geometry(self.snapshot, self.report)
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_cabin_aggregation_retains_full_boarded_membership(self):
|
|
cabin = self.cabin(40)
|
|
originals = copy.deepcopy(cabin['passengers'])
|
|
retained = {}
|
|
for y in (170,206):
|
|
group = sorted([r for r in originals if r['center'][1] == y], key=lambda r:(r['center'][0],r['key']))
|
|
drawn = group[:14] # 88px holds fourteen 6px cores, never fifteen.
|
|
for i, row in enumerate(drawn):
|
|
row.update(scale=1, at=[94+6*i,y+19])
|
|
retained[row['key']] = row
|
|
self.report['actor_aggregations'].append(dict(
|
|
members=[dict(key=r['key'], id=r['key'][6:], state=r['state'], cell=r['cell'], mode=r['mode']) for r in group],
|
|
drawn_keys=[r['key'] for r in drawn], at=[96,178], text='+6'))
|
|
cabin['passengers'] = [retained[r['key']] for r in originals if r['key'] in retained]
|
|
check.compare_cabin_geometry(self.snapshot, self.report)
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
self.assertEqual(sum(len(a['members']) for a in self.report['actor_aggregations']), 40)
|
|
for mutation in ('lost_identity','badge'):
|
|
report = copy.deepcopy(self.report)
|
|
if mutation == 'lost_identity': report['actor_aggregations'][0]['members'].pop()
|
|
else: report['actor_aggregations'][0]['at'][0] += 1
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
|
check.compare_actor_geometry(self.snapshot, report)
|
|
|
|
|
|
class CabinLandingTests(unittest.TestCase):
|
|
"""Constructed raw topology; explicit expected draws, never OS evidence."""
|
|
setUp = AnchoredGeometryTests.setUp
|
|
person = AnchoredGeometryTests.person
|
|
drawing = AnchoredGeometryTests.drawing
|
|
|
|
def lift(self, floor=0):
|
|
self.runtime['elevators']['shafts'] = [dict(
|
|
config=dict(shaft_id='lift', stops=[[1,0,0], [1,1,0]], width=2, capacity=4),
|
|
current_floor=0, phase='PICKUP', boarding_floor=1, phase_origin_floor=0,
|
|
remaining_minutes=10*(1-floor), phase_total_minutes=10, operational=True, boarded=[])]
|
|
self.report['elevator_tracks'] = {'lift': dict(floor=floor, **{'from': floor}, at=0)}
|
|
# The transform is 64x96; the full two-cell cabin is 96x76.
|
|
y = check.math.copysign(check.math.floor(abs(174-96*floor)+.5), 174-96*floor)
|
|
self.report['cabins'] = [dict(shaft_id='lift', rect=[156,y,96,76], pixel=4,
|
|
operational=True, doors_open=False, badge_at=[156,y-14], passengers=[])]
|
|
synthetic_repair28_fields(self.report, self.snapshot)
|
|
|
|
def test_moving_guest_same_floor_and_old_position_negative(self):
|
|
self.lift()
|
|
self.person('guest/g', [1,0,0])
|
|
self.drawing('guest/g', [108,214])
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
bad = copy.deepcopy(self.report)
|
|
bad['actor_drawings'][0].update(center=[172,214], at=[148,199])
|
|
with self.assertRaisesRegex(ValueError, 'non-passenger inside cabin'):
|
|
check.compare_actor_geometry(self.snapshot, bad)
|
|
|
|
def test_transit_from_lift_to_corridor_is_relocated_before_jitter(self):
|
|
self.lift()
|
|
self.person('guest/g', [0,0,0])
|
|
self.report.update(paused=False, animation_time=.21)
|
|
self.report['actor_tracks']['guest/g']['from'] = [1,0,0]
|
|
row = self.drawing('guest/g', [108,214], 'walk')
|
|
row['frame'] += 2
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
bad = copy.deepcopy(self.report)
|
|
bad['actor_drawings'][0]['center'][0] += 1
|
|
with self.assertRaisesRegex(ValueError, 'actor fitted center'):
|
|
check.compare_actor_geometry(self.snapshot, bad)
|
|
|
|
def test_carrier_and_raised_patient_follow_relocated_region(self):
|
|
self.lift()
|
|
self.person('staff/carrier', [1,0,0], 'AVAILABLE')
|
|
self.person('staff/patient', [1,0,0], 'INCAPACITATED')
|
|
self.runtime['staff']['tasks'] = [dict(task_type='rescue_transport', state='MOVING',
|
|
assigned_staff_id='carrier', target_entity_id='patient', rescue_facility_id='')]
|
|
self.report['carried_patients'] = {'patient': 'carrier'}
|
|
self.drawing('staff/carrier', [108,214])
|
|
self.drawing('staff/patient', [108,166], 'collapsed')
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
self.assertTrue(all(not check.intersects(check.actor_identity_core(r), self.report['cabins'][0]['rect'])
|
|
for r in self.report['actor_drawings']))
|
|
|
|
def test_fractional_moving_cabin_overlaps_floor_above(self):
|
|
self.lift(.8)
|
|
self.person('guest/g', [1,1,0])
|
|
self.drawing('guest/g', [108,118])
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_no_lane_aggregates_identity_and_never_drops_silently(self):
|
|
self.lift()
|
|
self.runtime['world']['walkable_cells'] = [[1,0,0], [2,0,0]]
|
|
self.person('guest/g', [1,0,0])
|
|
self.report['actor_aggregations'] = [dict(members=[dict(key='guest/g', id='g', state=3,
|
|
cell=[1,0,0], mode='idle')], drawn_keys=[], at=[60,150], text='+1')]
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
for mutation in ('badge', 'identity', 'hidden'):
|
|
bad = copy.deepcopy(self.report)
|
|
if mutation == 'badge': bad['actor_aggregations'][0]['at'][0] += 1
|
|
if mutation == 'identity': bad['actor_aggregations'][0]['members'] = []
|
|
if mutation == 'hidden':
|
|
bad['actor_aggregations'] = []
|
|
bad['queue_hidden'] = {'guest/g': True}
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
|
check.compare_actor_geometry(self.snapshot, bad)
|
|
self.person('guest/h', [1,0,0])
|
|
aggregate = self.report['actor_aggregations'][0]
|
|
aggregate['members'].append(dict(key='guest/h', id='h', state=3, cell=[1,0,0], mode='idle'))
|
|
aggregate['text'] = '+2'
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_reserved_lane_prefers_free_then_shares_with_queue(self):
|
|
self.lift()
|
|
self.person('guest/q', [1,0,0], 'QUEUING')
|
|
self.person('guest/g', [1,0,0])
|
|
self.runtime['elevators']['trips'] = [dict(request_id='queue', actor_id='guest:q', state='QUEUED',
|
|
shaft_id='lift', connector_id='lift.link.0.1', enqueued_at=0,
|
|
**{'from': [1,0,0], 'to': [1,1,0]})]
|
|
self.report.update(queue_positions={'guest/q':[108,214]},
|
|
landing_badges=[dict(at=[60,150], text='待ち1人', cell=[1,0,0],
|
|
members=['guest/q'], visible_count=1, overflow=0)])
|
|
self.drawing('guest/q', [108,214], 'wait')
|
|
self.drawing('guest/g', [300,214])
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
self.runtime['world']['walkable_cells'].remove([3,0,0])
|
|
self.report['actor_drawings'][0].update(at=[100,199])
|
|
self.report['actor_drawings'][1].update(center=[108,214], at=[82,199])
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
def test_flags_drop_and_unnecessary_relocation_fail(self):
|
|
self.lift()
|
|
self.person('guest/g', [1,0,0])
|
|
self.drawing('guest/g', [108,214])
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
for mutation in ('riding', 'region', 'boarded', 'drop'):
|
|
bad = copy.deepcopy(self.report)
|
|
if mutation == 'riding': bad['actor_drawings'][0]['riding'] = True
|
|
if mutation == 'region':
|
|
bad['actor_drawings'][0]['region'] = 'landing'
|
|
if mutation == 'boarded': bad['boarded']['guest/g'] = True
|
|
if mutation == 'drop': bad['actor_drawings'] = []
|
|
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
|
check.compare_actor_geometry(self.snapshot, bad)
|
|
# A free corridor actor must remain at its actual cell, even beside a lift.
|
|
self.runtime['guests']['guests'][0]['current_cell'] = [3,0,0]
|
|
self.report['actor_tracks']['guest/g'].update(cell=[3,0,0], **{'from': [3,0,0]})
|
|
with self.assertRaisesRegex(ValueError, 'actor fitted'):
|
|
check.compare_actor_geometry(self.snapshot, self.report)
|
|
|
|
|
|
class ComparisonTests(unittest.TestCase):
|
|
def pair(self):
|
|
declaration = dict(metric='sum_destination_arrival_minutes_entire_cohort',
|
|
arrivals=[dict(id='g', offset=0)], window=2, to=[2, 1, 0],
|
|
improvement=dict(definition_id='guest_elevator', origin=[4, 0, 0]))
|
|
a = dict(declaration=declaration, initial=dict(seed=1))
|
|
a['arrival-g'] = dict(offset=0, payload=dict(guest_id='g'), result=dict(code='OK'))
|
|
for minute in range(3):
|
|
a['tick-%03d' % minute] = dict(minute=minute, guests=dict(guests=[dict(guest_id='g',
|
|
current_cell=[2, 1, 0] if minute == 2 else [2, 0, 0])]))
|
|
b = copy.deepcopy(a)
|
|
b['build-0'] = dict(payload=declaration['improvement'], result=dict(code='OK'))
|
|
b['tick-001']['guests']['guests'][0]['current_cell'] = [2, 1, 0]
|
|
return a, b
|
|
|
|
def test_strict_metric_from_typed_cells(self):
|
|
a, b = self.pair()
|
|
self.assertEqual(check.compare_layouts(a, b)['B'], 1)
|
|
for mutation in ('demand', 'incomplete', 'no_improvement', 'extra_build'):
|
|
with self.subTest(mutation=mutation):
|
|
a, b = self.pair()
|
|
if mutation == 'demand':
|
|
b['arrival-g']['payload']['guest_id'] = 'other'
|
|
elif mutation == 'incomplete':
|
|
for minute in (1, 2):
|
|
b['tick-%03d' % minute]['guests']['guests'][0]['current_cell'] = [2, 0, 0]
|
|
elif mutation == 'no_improvement':
|
|
b['tick-001'] = copy.deepcopy(a['tick-001'])
|
|
else:
|
|
b['build-1'] = copy.deepcopy(b['build-0'])
|
|
with self.assertRaises(ValueError):
|
|
check.compare_layouts(a, b)
|
|
|
|
|
|
class OfflineGeometryTests(unittest.TestCase):
|
|
"""Synthetic calibration examples; no OS or acceptance credit."""
|
|
|
|
def setUp(self):
|
|
self.calibration = dict(window_size=[100, 80], window_position=[240, 150],
|
|
screenshot_pixels=[200, 190], backing_scale=2,
|
|
canvas_stretch=1, titlebar_pixels=30)
|
|
self.request = dict(window_size=[100, 80], window_position=[240, 150],
|
|
screen_scale=2, content_scale_factor=1, target_pid=123)
|
|
self.clicks = [dict(target_pid=123, events=[dict(action='click',
|
|
coordinates=[20, 70], calibration=copy.deepcopy(self.calibration),
|
|
point=dict(screen=[10, 20], viewport=[10, 20],
|
|
viewport_to_screen=[1, 0, 0, 1, 0, 0]))])]
|
|
|
|
def transform(self):
|
|
return check.screenshot_transform(self.calibration, self.request, [200, 190], self.clicks)
|
|
|
|
def test_uses_measured_scale_origin_not_desktop_position(self):
|
|
self.assertEqual(check.affine_rect(self.transform(), [10, 20, 5, 7]), [20, 70, 10, 14])
|
|
|
|
def test_shifted_titlebar_fails_independent_click(self):
|
|
self.calibration['titlebar_pixels'] += 1
|
|
self.calibration['screenshot_pixels'][1] += 1
|
|
self.clicks[0]['events'][0]['calibration'] = copy.deepcopy(self.calibration)
|
|
with self.assertRaises(ValueError):
|
|
check.screenshot_transform(self.calibration, self.request, [200, 191], self.clicks)
|
|
|
|
def test_wrong_click_offset_fails(self):
|
|
self.clicks[0]['events'][0]['coordinates'][1] -= 30
|
|
with self.assertRaisesRegex(ValueError, 'click screenshot offset'):
|
|
self.transform()
|
|
|
|
def test_wrong_pid_cannot_calibrate(self):
|
|
self.clicks[0]['target_pid'] += 1
|
|
with self.assertRaisesRegex(ValueError, 'missing independent click'):
|
|
self.transform()
|
|
|
|
def test_nonfinite_and_false_scale_rejected(self):
|
|
for scale in (False, float('nan'), 0, -1):
|
|
self.calibration['backing_scale'] = scale
|
|
with self.assertRaisesRegex(ValueError, 'invalid screenshot calibration'):
|
|
self.transform()
|
|
|
|
def test_all_visibility_cases_are_accounted_for(self):
|
|
self.assertEqual(check.rectangle_eligibility([2, 3, 4, 5], [10, 10]),
|
|
('visible', [2, 3, 4, 5]))
|
|
self.assertEqual(check.rectangle_eligibility([-2, 3, 4, 5], [10, 10]),
|
|
('clipped', [0, 3, 2, 5]))
|
|
self.assertEqual(check.rectangle_eligibility([10, 3, 4, 5], [10, 10])[0], 'outside')
|
|
self.assertEqual(check.rectangle_eligibility([2, 3, 0, 5], [10, 10])[0], 'outside')
|
|
|
|
def test_invalid_rectangles_rejected(self):
|
|
for rect in ([0, 0, -1, 4], [float('nan'), 0, 1, 4]):
|
|
with self.assertRaisesRegex(ValueError, 'invalid clipping rectangle'):
|
|
check.rectangle_eligibility(rect, [10, 10])
|
|
|
|
def test_noise_is_measured_and_does_not_assert_class(self):
|
|
before = (2, 2, [[(10, 20, 30)]*2 for _ in range(2)])
|
|
after = copy.deepcopy(before)
|
|
after[2][1][1] = (10, 27, 30)
|
|
noise = check.paired_crop_noise(before, after, [0, 0, 2, 2])
|
|
self.assertEqual(noise, dict(samples=4, max_channel_delta=7,
|
|
mean_channel_delta=1.75, unchanged=3))
|
|
self.assertNotIn('pass', noise)
|
|
self.assertNotIn('state', noise)
|
|
|
|
def test_wrong_dimensions_and_empty_crop_rejected(self):
|
|
image = (1, 1, [[(0, 0, 0)]])
|
|
with self.assertRaisesRegex(ValueError, 'paired image dimensions'):
|
|
check.paired_crop_noise(image, (2, 1, []), [0, 0, 1, 1])
|
|
with self.assertRaisesRegex(ValueError, 'empty noise crop'):
|
|
check.paired_crop_noise(image, image, [1, 0, 1, 1])
|
|
|
|
def test_report_positions_and_source_provenance_are_recomputed(self):
|
|
source = 'scripts/ui/pixel_people.gd'
|
|
report = dict(rooms={}, landing_badges=[], cabins=[], resting_staff={}, badges=[],
|
|
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1,
|
|
mode='idle', frame=0, flip=False)])
|
|
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
|
(check.ROOT/source).read_text())))
|
|
audit = dict(rows=[dict(category='staff', id='staff/s', rect=[1, 1, 16, 18],
|
|
style_source=source, style_colors=colors)])
|
|
check.validate_drawing_rows(report, audit)
|
|
for change in ('shift', 'identity', 'missing', 'palette'):
|
|
tampered = copy.deepcopy(audit)
|
|
if change == 'shift': tampered['rows'][0]['rect'][0] += 1
|
|
if change == 'identity': tampered['rows'][0]['category'] = 'guest'
|
|
if change == 'missing': tampered['rows'] = []
|
|
if change == 'palette': tampered['rows'][0]['style_colors'] = ['000000']
|
|
with self.subTest(change=change), self.assertRaises(ValueError):
|
|
check.validate_drawing_rows(report, tampered)
|
|
|
|
|
|
class FrameReviewTests(unittest.TestCase):
|
|
def test_frame_raw_context_valid_malformed_and_missing_rest(self):
|
|
fixture = SpatialTests()
|
|
fixture.setUp()
|
|
fixture.moving_four()
|
|
fixture.report.update(viewport=[216,236], traffic=[])
|
|
audit = fixture.drawing_audit()
|
|
for row in audit['rows']:
|
|
row.update(viewport_rect=row['rect'][:], screen_rect=row['rect'][:])
|
|
for context, rest, passes in ((fixture.snapshot,None,True), ({},None,False)):
|
|
result = check.frame_rows(fixture.report, audit, self.request, dict(calibration=self.calibration),
|
|
[self.image], self.clicks, state=context, rest_audit=rest)
|
|
self.assertEqual(not result['errors'], passes)
|
|
fixture.report['resting_staff'] = {'s':'a'}
|
|
for rest in (None, [], {}):
|
|
result = check.frame_rows(fixture.report, audit, self.request, dict(calibration=self.calibration),
|
|
[self.image], self.clicks, state=fixture.snapshot, rest_audit=rest)
|
|
self.assertFalse(result['mechanical_pass'])
|
|
fixture.report['resting_staff'] = {}
|
|
result = check.frame_rows(fixture.report, audit, self.request, dict(calibration=self.calibration),
|
|
[self.image], self.clicks)
|
|
self.assertFalse(result['mechanical_pass'])
|
|
self.assertIn('core overlap', str(result['errors']))
|
|
|
|
def test_frame_with_valid_occupied_rest_authority(self):
|
|
fixture = AnchoredGeometryTests()
|
|
fixture.setUp()
|
|
rest = fixture.occupied_rest()
|
|
rows = []
|
|
for category, identifier, rect, source, text in check.drawing_rectangle_rows(fixture.report):
|
|
self.assertIsNone(text)
|
|
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"', (check.ROOT/source).read_text())))
|
|
rows.append(dict(category=category, id=identifier, rect=rect, viewport_rect=rect[:],
|
|
screen_rect=rect[:], style_source=source, style_colors=colors))
|
|
result = check.frame_rows(fixture.report, dict(rows=rows), self.request, dict(calibration=self.calibration),
|
|
[self.image], self.clicks, state=fixture.snapshot, rest_audit=rest)
|
|
self.assertEqual(result['errors'], [])
|
|
rest['path_service_sha256'] = '0'*64
|
|
result = check.frame_rows(fixture.report, dict(rows=rows), self.request, dict(calibration=self.calibration),
|
|
[self.image], self.clicks, state=fixture.snapshot, rest_audit=rest)
|
|
self.assertFalse(result['mechanical_pass'])
|
|
self.assertIn('rest PathService source pin', result['errors'])
|
|
|
|
def test_os_offline_raw_context_binding_and_hashes(self):
|
|
def typed(value):
|
|
if isinstance(value, dict): return dict(type='Dictionary', entries=[[typed(k),typed(v)] for k,v in value.items()])
|
|
if isinstance(value, list): return dict(type='Array', items=[typed(v) for v in value])
|
|
return dict(type={str:'String',int:'int'}[type(value)], text=json.dumps(value))
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
stream = Path(temporary)
|
|
(stream/'objects').mkdir()
|
|
def put(value):
|
|
raw = json.dumps(typed(value)).encode()
|
|
digest = hashlib.sha256(raw).hexdigest()
|
|
(stream/'objects'/(digest+'.json')).write_bytes(raw)
|
|
return digest
|
|
report = {'actor_drawings':[]}
|
|
state = {'systems':{'runtime':{}}}
|
|
rest = {'staff':[], 'path_service_sha256':check.sha(check.ROOT/'scripts/world/path_service.gd')}
|
|
capture = dict(report=put(report), state=put(state), rest_audit=put(rest))
|
|
self.assertEqual(check.drawing_context(stream, capture, report), (state,rest))
|
|
for field in ('state','rest_audit','report'):
|
|
missing = dict(capture)
|
|
del missing[field]
|
|
with self.assertRaises(ValueError): check.drawing_context(stream, missing, report)
|
|
for field in ('state','rest_audit'):
|
|
bad = dict(capture, **{field:put({})})
|
|
with self.assertRaises(ValueError): check.drawing_context(stream, bad, report)
|
|
with self.assertRaises(ValueError): check.drawing_context(stream, capture, {'wrong':1})
|
|
with self.assertRaises(FileNotFoundError):
|
|
check.drawing_context(stream, dict(capture, state='0'*64), report)
|
|
(stream/'objects'/(capture['state']+'.json')).write_text('{}')
|
|
with self.assertRaisesRegex(ValueError, 'object hash'): check.drawing_context(stream, capture, report)
|
|
|
|
def setUp(self):
|
|
OfflineGeometryTests.setUp(self)
|
|
self.request.update(local_to_viewport=[1, 0, 0, 1, 0, 0],
|
|
viewport_to_screen=[1, 0, 0, 1, 0, 0],
|
|
local_to_screen=[1, 0, 0, 1, 0, 0])
|
|
self.report = dict(layer=0, viewport=[100, 80], rooms={}, cabins=[],
|
|
landing_badges=[], badges=[], resting_staff={}, traffic=[],
|
|
actor_drawings=[dict(key='staff/s', staff=True, at=[1, 1], scale=1, state=0,
|
|
mode='idle', frame=0, flip=False)])
|
|
source = 'scripts/ui/pixel_people.gd'
|
|
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
|
(check.ROOT/source).read_text())))
|
|
self.audit = dict(rows=[dict(category='staff', id='staff/s', rect=[1, 1, 16, 18],
|
|
viewport_rect=[1, 1, 16, 18], screen_rect=[1, 1, 16, 18],
|
|
style_source=source, style_colors=colors)])
|
|
self.image = (200, 190, [[(x % 256, y % 256, 0) for x in range(200)] for y in range(190)])
|
|
|
|
def frame(self):
|
|
return check.frame_rows(self.report, self.audit, self.request,
|
|
dict(calibration=self.calibration), [self.image], self.clicks)
|
|
|
|
def test_frame_without_raw_state_explicitly_denies_transit_exemptions(self):
|
|
self.report['actor_drawings'][0]['in_transit'] = True
|
|
self.assertEqual(self.frame()['transit_authority'],
|
|
'absent: fail closed, no transit exemptions')
|
|
|
|
def test_blank_crop_fails_without_threshold(self):
|
|
self.image = (200, 190, [[(0, 0, 0)] * 200 for _ in range(190)])
|
|
result = self.frame()
|
|
self.assertFalse(result['mechanical_pass'])
|
|
self.assertIn('blank crop', result['rows'][0]['failures'])
|
|
|
|
def test_missing_extra_and_unaudited_traffic_fail(self):
|
|
for change in ('missing', 'extra', 'traffic'):
|
|
with self.subTest(change=change):
|
|
original = copy.deepcopy(self.audit)
|
|
if change == 'missing': self.audit['rows'].clear()
|
|
if change == 'extra': self.audit['rows'] *= 2
|
|
if change == 'traffic':
|
|
self.report['traffic'] = [dict(cell=[-6, 2, 0], rect=[1, 1, 5, 5],
|
|
traffic=dict(current_density=1, temporary_block=False))]
|
|
result = self.frame()
|
|
self.assertFalse(result['mechanical_pass'])
|
|
self.assertEqual(len(result['rows']), len(self.audit['rows']))
|
|
self.assertIn('offline rectangle coverage', result['errors'])
|
|
self.audit = original
|
|
|
|
def test_dense_or_blocked_traffic_row_closes_audit_gap(self):
|
|
source = 'scenes/main/world_preview.gd'
|
|
colors = list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
|
(check.ROOT/source).read_text())))
|
|
for density, blocked in ((1, False), (0, True), (0, False)):
|
|
with self.subTest(density=density, blocked=blocked):
|
|
self.report['traffic'] = [dict(cell=[-6, 2, 0], rect=[1, 1, 5, 5],
|
|
traffic=dict(current_density=density, temporary_block=blocked))]
|
|
active = density > 0 or blocked
|
|
missing = self.frame()
|
|
self.assertEqual(missing['mechanical_pass'], not active)
|
|
if active:
|
|
self.assertIn('offline rectangle coverage', missing['errors'])
|
|
self.audit['rows'].append(dict(category='traffic', id='-6,2,0',
|
|
rect=[1, 1, 5, 5], viewport_rect=[1, 1, 5, 5],
|
|
screen_rect=[1, 1, 5, 5], style_source=source, style_colors=colors))
|
|
complete = self.frame()
|
|
self.assertTrue(complete['mechanical_pass'], complete['errors'])
|
|
self.audit['rows'].pop()
|
|
|
|
def test_wrong_state_enum_and_name_fail(self):
|
|
source = 'scripts/ui/pixel_tower_art.gd'
|
|
self.report['actor_drawings'] = []
|
|
room = dict(rect=[1, 1, 16, 18], state=3, state_name='NO_STAFF', operation=0)
|
|
self.report['rooms'] = {'r': room}
|
|
self.audit['rows'][0].update(category='room', id='r', style_source=source,
|
|
style_colors=list(dict.fromkeys(check.re.findall(r'Color\("([0-9a-fA-F]{6})"',
|
|
(check.ROOT/source).read_text()))))
|
|
self.assertEqual(self.frame()['rows'][0]['operation'], 'INACTIVE')
|
|
for state, name in ((99, 'NO_STAFF'), (3, 'OPEN'), (True, 'NO_ACCESS')):
|
|
room.update(state=state, state_name=name)
|
|
with self.assertRaises(ValueError): self.frame()
|
|
|
|
def test_frame_rejects_affine_and_calibration_shift(self):
|
|
self.request['local_to_screen'][4] += 1
|
|
with self.assertRaisesRegex(ValueError, 'affine composition'): self.frame()
|
|
self.request['local_to_screen'][4] -= 1
|
|
self.clicks[0]['events'][0]['coordinates'][1] += 1
|
|
with self.assertRaisesRegex(ValueError, 'click screenshot offset'): self.frame()
|
|
|
|
def test_eligible_crop_fully_outside_image_fails(self):
|
|
for key in ('viewport_to_screen', 'local_to_screen'):
|
|
self.request[key][4] = 300
|
|
self.audit['rows'][0]['screen_rect'][0] += 300
|
|
result = self.frame()
|
|
self.assertFalse(result['mechanical_pass'])
|
|
self.assertIn('eligible crop outside image', result['rows'][0]['failures'])
|
|
|
|
def test_manifest_crop_and_independent_review_binding(self):
|
|
with tempfile.TemporaryDirectory() as temporary:
|
|
directory = Path(temporary)
|
|
manifest, digest = check.write_frame_review(directory, self.frame(),
|
|
[dict(whole_image_sha256='a'*64, raw_sha256='b'*64, conversion_sha256='c'*64)])
|
|
self.assertEqual(check.sha(directory/'manifest.json'), digest)
|
|
crop = manifest['rows'][0]['crops'][0]
|
|
self.assertEqual(check.sha(directory/crop['path']), crop['sha256'])
|
|
self.assertEqual(check.png_pixels((directory/crop['path']).read_bytes())[:2], (32, 36))
|
|
verdict_path = directory/'independent-verdict.json'
|
|
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), set())
|
|
verdict = dict(manifest_sha256=digest, reviewer='independent-opus-direct-image',
|
|
rows=[dict(row_index=0, category=True, state=True, position=True)])
|
|
for change in ('wrong_sha', 'missing_row', 'extra_row', 'rejected_state'):
|
|
bad = copy.deepcopy(verdict)
|
|
if change == 'wrong_sha': bad['manifest_sha256'] = '0'*64
|
|
if change == 'missing_row': bad['rows'] = []
|
|
if change == 'extra_row': bad['rows'] *= 2
|
|
if change == 'rejected_state': bad['rows'][0]['state'] = False
|
|
verdict_path.write_text(json.dumps(bad))
|
|
with self.subTest(change=change), self.assertRaises(ValueError):
|
|
check.reviewed_pixel_categories(manifest, digest, verdict_path)
|
|
verdict_path.write_text(json.dumps(verdict))
|
|
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), {'staff'})
|
|
manifest['mechanical_pass'] = False
|
|
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), set())
|
|
|
|
|
|
class Repair36LabelTests(unittest.TestCase):
|
|
inputs = check.ROOT / 'tests/fixtures/p23-03/recorded-badge-negative.json.gz'
|
|
|
|
def _recorded_payloads(self):
|
|
bundle = json.loads(gzip.decompress(self.inputs.read_bytes()))
|
|
expected = {
|
|
'request': (655285, 'f9cd2d5ff9dd63d9cf7ae4a295ccef8eea16f75ac784696dc40906703731e1d8'),
|
|
'state': (1867011, '724d8fe362ba3e05eddd83c4b7c54da101899df6cd91ae88250feea3d4feff82'),
|
|
'manifest': (75726, 'a98782ec1263aba400d542862dd084d2f1dd4cf7a0851cf7f0679867e545a338'),
|
|
}
|
|
payloads = {}
|
|
for name, (length, digest) in expected.items():
|
|
payload = bundle['files'][name]['raw_utf8'].encode('utf-8')
|
|
check.require(len(payload) == length and hashlib.sha256(payload).hexdigest() == digest,
|
|
'recorded fixture payload mismatch: ' + name)
|
|
payloads[name] = payload
|
|
return payloads
|
|
|
|
def recorded(self):
|
|
payloads = self._recorded_payloads()
|
|
request = json.loads(payloads['request'])
|
|
typed_state = json.loads(payloads['state'])
|
|
check.require(typed_state.get('type') == 'Dictionary', 'whole dictionary required')
|
|
return request, check.decode(typed_state), check.decode(request['report'])
|
|
|
|
def resolved(self, report):
|
|
result = copy.deepcopy(report)
|
|
for collection, index, fields in check.observation_label_layout(report):
|
|
result[collection][index].update(fields)
|
|
return result
|
|
|
|
def test_recorded_negative_and_positive_with_raw_semantics(self):
|
|
request, state, old = self.recorded()
|
|
rectangles = check.drawing_rectangle_rows(old)
|
|
self.assertTrue(check.intersects(rectangles[5][2], rectangles[26][2]))
|
|
with self.assertRaises(ValueError):
|
|
check.validate_observation_labels(old)
|
|
report = self.resolved(old)
|
|
check.validate_observation_labels(report)
|
|
check.compare_queues(state, report)
|
|
check.compare_badges(state, report)
|
|
check.compare_actor_geometry(state, report)
|
|
queue = next(b for b in report['landing_badges'] if b['visible_count'] == 0)
|
|
manifest = json.loads(self._recorded_payloads()['manifest'])
|
|
original = manifest['rows'][5]
|
|
self.assertEqual(queue['anchor'], original['local_rect'][:2])
|
|
self.assertEqual(original['screenshot_rect'][:2], [642,469])
|
|
self.assertEqual(queue['members'], next(b['members'] for b in old['landing_badges'] if b['visible_count'] == 0))
|
|
|
|
def test_recorded_forgery_controls(self):
|
|
_, state, old = self.recorded()
|
|
good = self.resolved(old)
|
|
for attack in ('at', 'displaced_by', 'count', 'hidden', 'anchor'):
|
|
report = copy.deepcopy(good)
|
|
row = report['actor_aggregations'][0]
|
|
if attack == 'at': row['at'][0] += 1
|
|
elif attack == 'displaced_by': row['displaced_by'] = []
|
|
elif attack == 'count': row['text'] = '+'
|
|
elif attack == 'hidden': report['actor_aggregations'] = []
|
|
else: row['anchor'][0] += 1
|
|
with self.subTest(attack=attack), self.assertRaises(ValueError):
|
|
check.validate_observation_labels(report)
|
|
check.compare_queues(state, report)
|
|
check.compare_actor_geometry(state, report)
|
|
|
|
def test_generic_order_and_edge_contact(self):
|
|
for kinds in [('business', 'aggregation'), ('supply', 'landing'),
|
|
('business', 'supply', 'landing', 'aggregation')]:
|
|
report = dict(cabins=[], badges=[], landing_badges=[], actor_aggregations=[])
|
|
for kind in kinds:
|
|
key = 'badges' if kind in ('business', 'supply') else 'landing_badges' if kind == 'landing' else 'actor_aggregations'
|
|
report[key].append(dict(kind=kind, at=[10,20], text='abc'))
|
|
result = self.resolved(report)
|
|
check.validate_observation_labels(result)
|
|
positions = [fields['at'] for _, _, fields in check.observation_label_layout(result)]
|
|
self.assertEqual(positions, [[10+36*i,20] for i in range(len(kinds))])
|
|
report = dict(cabins=[dict(rect=[0,50,20,20], badge_at=[10,20], badge='abc')],
|
|
badges=[], landing_badges=[dict(at=[10,20], text='x')], actor_aggregations=[])
|
|
self.assertEqual(self.resolved(report)['landing_badges'][0]['at'], [46,20])
|
|
report['landing_badges'][0]['at'] = [46,20]
|
|
self.assertEqual(self.resolved(report)['landing_badges'][0]['displaced_by'], [])
|
|
|
|
def test_future_review_requires_indexed_judgments(self):
|
|
manifest = dict(rows=[dict(row_index=7, category='queue', eligibility='visible', mechanical_pass=True)], mechanical_pass=True)
|
|
verdict = dict(manifest_sha256='digest', reviewer='independent-opus-direct-image',
|
|
state_bits='1', position_bits='1', category_bits='1')
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
path = Path(folder)/'verdict.json'
|
|
path.write_text(json.dumps(verdict))
|
|
with self.assertRaisesRegex(ValueError, 'indexed judgments'):
|
|
check.reviewed_pixel_categories(manifest, 'digest', path, require_judgments=True)
|
|
verdict['judgments'] = [dict(row_index=7, category=True, state=True, position=True)]
|
|
path.write_text(json.dumps(verdict))
|
|
self.assertEqual(check.reviewed_pixel_categories(manifest, 'digest', path, require_judgments=True), {'queue'})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|