lazyBoy/tests/file-process-proof.test.py

87 lines
5.0 KiB
Python

"""Quiescence evidence must distinguish process exit from unavailable identity."""
import os
import hashlib
import json
import sqlite3
import subprocess
from pathlib import Path
import unittest
from unittest.mock import patch
SOURCE = Path(__file__).resolve().parents[1] / 'crates/supervisor/src/runner_files.py'
class FileProcessProof(unittest.TestCase):
def setUp(self):
self.boot = '00000000-0000-0000-0000-000000000000:123'
self.module = {'process_info': lambda pid: {'start': '456', 'state': 'S'}, 'boot_identity': lambda: self.boot}
exec(compile(SOURCE.read_text(), str(SOURCE), 'exec'), self.module)
self.reference = {'pid': os.getpid(), 'start': '456', 'boot': self.boot}
def test_live_reused_and_unavailable_identity(self):
finished = self.module['file_process_finished']
self.assertFalse(finished(self.reference))
self.assertTrue(finished({**self.reference, 'start': '455'}))
self.assertIsNone(finished({**self.reference, 'boot': self.boot[:-3] + '999'}))
self.assertIsNone(finished({**self.reference, 'boot': 'invalid'}))
with patch.object(os, 'stat', side_effect=PermissionError()):
self.assertIsNone(finished(self.reference))
with patch.object(os, 'stat', side_effect=FileNotFoundError()):
self.assertTrue(finished(self.reference))
self.module['boot_identity'] = lambda: (_ for _ in ()).throw(FileNotFoundError())
self.assertIsNone(finished(self.reference), 'missing boot data is not proof of exit')
def test_legacy_and_controller_only_evidence(self):
quiescent = self.module['file_quiescent']
self.assertIsNone(quiescent({}))
self.assertFalse(quiescent({'handoff_required': True, 'controller': self.reference}))
self.assertFalse(quiescent({'handoff_required': True, 'controller': {**self.reference, 'start': '455'}, 'execution': self.reference}))
self.assertTrue(quiescent({'handoff_required': True, 'execution': {**self.reference, 'start': '455'}}))
def test_controller_exit_refreshes_child_committed_after_first_read(self):
identity = {'computer_id': 'fixture', 'bot_id': 'race', 'generation': 1}
encoded = self.module['file_identity'](identity)
key = hashlib.sha256((encoded + ':operation').encode()).hexdigest()
metadata = {'action': 'write', 'path': 'file', 'desiredHash': 'a' * 64,
'handoff_required': True, 'controller': {**self.reference, 'start': '455'}}
with sqlite3.connect(':memory:') as db:
self.module['file_journal_schema'](db)
db.execute('INSERT INTO file_operations VALUES (?,?,?,?,NULL,?,?)',
(key, encoded, 'digest', 'accepted', json.dumps(metadata), 0))
db.commit()
def observe_process(pid):
# The status SELECT already returned controller-only metadata;
# the controller commits its child and dies before /proc is read.
db.execute('UPDATE file_operations SET metadata=? WHERE id=?',
(json.dumps({**metadata, 'execution': self.reference}), key))
db.commit()
return {'start': '456', 'state': 'S'}
self.module['process_info'] = observe_process
fingerprint = subprocess.CompletedProcess([], 0, json.dumps({'path': 'file', 'kind': 'file', 'sha256': 'a' * 64}), '')
with patch.object(subprocess, 'run', return_value=fingerprint):
result = self.module['file_journal_handle'](db, {
'action': 'file_status', 'identity': identity,
'input': json.dumps({'operation_id': 'operation', 'readback': True, 'resolve': True})
}, '', lambda *_: False)
evidence = json.loads(result['stdout'])
self.assertEqual(result['status'], 'unknown')
self.assertTrue(evidence['desired_state_matches'])
self.assertFalse(evidence['helper_quiescent'], 'committed child is still alive')
stored = json.loads(db.execute('SELECT observation FROM file_observations').fetchone()[0])
self.assertFalse(stored['helper_quiescent'])
self.module['process_info'] = lambda pid: {'start': '456', 'state': 'S'}
db.execute('UPDATE file_operations SET metadata=? WHERE id=?', (json.dumps({**metadata,
'controller': self.reference, 'execution': {**self.reference, 'start': '455'}}), key))
db.commit()
with patch.object(subprocess, 'run', return_value=fingerprint):
result = self.module['file_journal_handle'](db, {
'action': 'file_status', 'identity': identity,
'input': json.dumps({'operation_id': 'operation', 'readback': True, 'resolve': True})
}, '', lambda *_: False)
self.assertEqual(result['status'], 'unknown', 'live controller can still commit its original receipt')
self.assertFalse(json.loads(result['stdout'])['controller_quiescent'])
if __name__ == '__main__':
unittest.main()