okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
75f8b53
main
1"""Catch premature trace release and observation after nested execution. 2 3The daemon cannot refuse trace writes or gate cells.finish acknowledgements, 4so these checks drive the real kernel through its owner channel. 5""" 6 7import unittest 8 9from remote_kernel_test import OwnerChannel 10 11INSPECT = """ 12import __main__ as kernel 13import asyncio, sys 14 15def trace_state(id): 16trace = kernel.ARCHIVES[id].trace 17return {'sealed': trace.sealed, 'before': len(trace.before), 18'activities': len(trace.activities), 'truncated': trace.truncated} 19 20async def release_late(): 21late_gate.set() 22await late_task 23return trace_state('child') 24""" 25EDIT = "open('note.txt', 'w').write('edited\\n')\nopen('note.txt').read()\n" 26 27 28class CellTracesTest (unittest .TestCase ): 29def setUp (self ): 30self .channel = OwnerChannel ([]) 31self .addCleanup (self .channel .close ) 32self .assertEqual (self .channel .recv ()["type" ],"ready" ) 33self .execute ("setup" ,INSPECT ) 34 35def execute (self ,id ,code ): 36self .channel .send ({"type" :"execute" ,"id" :id ,"code" :code }) 37trace = self .channel .wait_for ( 38lambda frame :frame ["type" ]== "trace" and frame ["id" ]== id 39 ) 40self .assertFalse ( 41any ( 42frame ["type" ]== "done" and frame ["id" ]== id 43for frame in self .channel .buffered 44 ) 45 ) 46done = self .channel .wait_for ( 47lambda frame :frame ["type" ]== "done" and frame ["id" ]== id 48 ) 49return trace ["trace" ],done 50 51def state (self ,id ): 52reply = self .channel .invoke ("inspect-" + id ,name = "trace_state" ,args = [id ]) 53self .assertTrue (reply ["ok" ],reply ) 54return reply ["value" ] 55 56def acknowledge (self ,call ,value = None ): 57self .channel .send ( 58 {"type" :"reply" ,"id" :call ["id" ],"value" : {"ok" :True ,"value" :value }} 59 ) 60 61def test_nested_seals_before_ack_and_late_task_cannot_refill (self ): 62self .channel .send ( 63 {"type" :"execute" ,"id" :"parent" ,"code" :"await cells.run('saved')" } 64 ) 65prepare = self .channel .wait_for ( 66lambda frame :frame .get ("method" )== "cells.prepare" 67 ) 68self .acknowledge ( 69prepare , 70 { 71"id" :"child" , 72"source" :EDIT 73+ """ 74late_gate = asyncio.Event() 75async def late_edit(): 76await late_gate.wait() 77open('note.txt', 'w').write('late\\n') 78sys.audit('open') 79kernel.albedo_trace.note('read', 'late') 80late_task = asyncio.create_task(late_edit()) 81""" , 82 }, 83 ) 84started = self .channel .wait_for ( 85lambda frame :frame .get ("method" )== "cells.started" 86 ) 87self .acknowledge (started ) 88frame = self .channel .wait_for ( 89lambda frame :frame ["type" ]== "trace" and frame ["id" ]== "child" 90 ) 91self .assertFalse ( 92any ( 93frame .get ("method" )== "cells.finish" for frame in self .channel .buffered 94 ) 95 ) 96finish = self .channel .wait_for ( 97lambda frame :frame .get ("method" )== "cells.finish" 98 ) 99expected = {"sealed" :True ,"before" :0 ,"activities" :0 ,"truncated" :False } 100self .assertEqual (self .state ("child" ),expected ) 101reply = self .channel .invoke ("late" ,name = "release_late" ) 102self .assertTrue (reply ["ok" ],reply ) 103self .assertEqual (reply ["value" ],expected ) 104self .assertIn ("+edited" ,frame ["trace" ]["changes" ][0 ]["diff" ]) 105self .acknowledge (finish ) 106self .channel .wait_for ( 107lambda frame :frame ["type" ]== "done" and frame ["id" ]== "parent" 108 ) 109self .assertEqual (self .state ("child" ),expected ) 110 111def test_delivery_failure_keeps_sealed_buffers_and_propagates (self ): 112_ ,done = self .execute ( 113"failures" , 114""" 115def fail_delivery(finalization=False): 116capture = kernel.Capture('undelivered') 117capture.trace.writing('missing.txt') 118capture.trace.activity('read', 'missing.txt') 119original_send = kernel.send 120original_snapshot = kernel.albedo_trace.snapshot 121def fail(*args): 122raise OSError('forced failure') 123try: 124if finalization: 125kernel.albedo_trace.snapshot = fail 126else: 127kernel.send = fail 128try: 129kernel.deliver_trace(capture) 130except OSError: 131pass 132else: 133raise AssertionError('failure did not propagate') 134finally: 135kernel.send = original_send 136kernel.albedo_trace.snapshot = original_snapshot 137trace = capture.trace 138assert trace.sealed and trace.before and trace.activities 139trace.writing('late.txt') 140trace.activity('read', 'late.txt') 141trace.renamed('missing.txt', 'late.txt') 142assert len(trace.before) == len(trace.activities) == 1 143try: 144trace.finish() 145except RuntimeError: 146pass 147else: 148raise AssertionError('finish allowed twice') 149fail_delivery() 150fail_delivery(True) 151""" , 152 ) 153self .assertEqual (done ["status" ],"ok" ,done ) 154 155def test_payload_is_detached_and_release_is_idempotent (self ): 156_ ,done = self .execute ( 157"detached" , 158""" 159trace = kernel.albedo_trace.Trace() 160try: 161trace.release() 162except RuntimeError: 163pass 164else: 165raise AssertionError('released an observing trace') 166trace.truncated = True 167trace.activity('read', 'original') 168trace.writing('missing.txt') 169payload = trace.finish() 170trace.activities[('read', 'original')]['target'] = 'mutated' 171trace.release() 172trace.release() 173assert payload['activities'] == [{'kind': 'read', 'target': 'original'}] 174assert not trace.before and not trace.activities 175assert payload['truncated'] and trace.truncated 176""" , 177 ) 178self .assertEqual (done ["status" ],"ok" ,done ) 179 180 181if __name__ == "__main__" : 182unittest .main ()