okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
79b55b3
main
1"""Catch retained image leaks and premature release during nested delivery. 2 3The daemon cannot inspect archived captures or pause/refuse cells.finish, so 4these checks use the real kernel through the shared owner channel. 5""" 6 7import unittest 8 9from remote_kernel_test import OwnerChannel 10 11PNG = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAIAAAA2iEnWAAAAAElFTkSuQmCC" 12IMAGE = f"show_image(__import__('base64').b64decode( { PNG !r } ))\n" 13INSPECT = """ 14import __main__ as kernel 15 16def capture_state(id): 17capture = kernel.ARCHIVES[id] 18return {'images': len(capture.images), 'text': output.read(id)} 19 20def finished_images(): 21return {id: len(done['images']) for id, done in kernel.FINISHED.items()} 22""" 23 24 25class CellImagesTest (unittest .TestCase ): 26def setUp (self ): 27self .channel = OwnerChannel ([]) 28self .addCleanup (self .channel .close ) 29self .assertEqual (self .channel .recv ()["type" ],"ready" ) 30self .execute ("setup" ,INSPECT ) 31 32def execute (self ,id ,code ): 33self .channel .send ({"type" :"execute" ,"id" :id ,"code" :code }) 34return self .channel .wait_for ( 35lambda frame :frame ["type" ]== "done" and frame ["id" ]== id 36 ) 37 38def state (self ,id ): 39reply = self .channel .invoke ("inspect-" + id ,name = "capture_state" ,args = [id ]) 40self .assertTrue (reply ["ok" ]) 41return reply ["value" ] 42 43def acknowledge (self ,call ,value = None ,refused = False ): 44answer = ( 45 {"ok" :False ,"code" :"refused" ,"message" :"finish refused" } 46if refused 47else {"ok" :True ,"value" :value } 48 ) 49self .channel .send ({"type" :"reply" ,"id" :call ["id" ],"value" :answer }) 50 51def finish_call (self ): 52started = self .channel .wait_for ( 53lambda frame :frame .get ("method" )== "cells.started" 54 ) 55self .acknowledge (started ) 56return self .channel .wait_for ( 57lambda frame :frame .get ("method" )== "cells.finish" 58 ) 59 60def test_delivered_success_error_and_cancellation_release_images (self ): 61for status ,ending in ( 62 ("ok" ,"" ), 63 ("error" ,"raise ValueError('execution failed')" ), 64 ("interrupted" ,"raise __import__('asyncio').CancelledError()" ), 65 ): 66with self .subTest (status = status ): 67done = self .execute (status ,IMAGE + "print('retained text')\n" + ending ) 68self .assertEqual (done ["status" ],status ) 69self .assertEqual (done ["images" ], [PNG ]) 70state = self .state (status ) 71self .assertEqual (state ["images" ],0 ) 72self .assertIn ("retained text" ,state ["text" ]) 73 74def test_nested_images_wait_for_acknowledgement_and_survive_refusal (self ): 75for refused in (False ,True ): 76with self .subTest (refused = refused ): 77child = f"child- { refused } " 78parent = "parent-" + child 79self .channel .send ( 80 { 81"type" :"execute" , 82"id" :parent , 83"code" :"await cells.run('saved')" , 84 } 85 ) 86prepare = self .channel .wait_for ( 87lambda frame :frame .get ("method" )== "cells.prepare" 88 ) 89self .acknowledge ( 90prepare , 91 {"id" :child ,"source" :IMAGE + "raise ValueError('child failed')" }, 92 ) 93finish = self .finish_call () 94self .assertEqual (finish ["args" ]["outcome" ]["status" ],"error" ) 95self .assertEqual (finish ["args" ]["outcome" ]["images" ], [PNG ]) 96self .assertEqual (self .state (child )["images" ],1 ) 97self .assertEqual (self .state (parent )["images" ],1 ) 98self .acknowledge (finish ,refused = refused ) 99done = self .channel .wait_for ( 100lambda frame :frame ["type" ]== "done" and frame ["id" ]== parent 101 ) 102self .assertEqual (done ["images" ], [PNG ]) 103self .assertEqual (self .state (child )["images" ],int (refused )) 104self .assertEqual (self .state (parent )["images" ],0 ) 105 106def test_replays_keep_images_within_a_budget_newest_first (self ): 107self .execute ("budget" ,f"kernel.FINISHED_IMAGE_BYTES = { len ( PNG ) * 2 } " ) 108for id in ("first" ,"second" ,"third" ): 109self .assertEqual (self .execute (id ,IMAGE )["images" ], [PNG ]) 110reply = self .channel .invoke ("finished" ,name = "finished_images" ) 111self .assertEqual ( 112 {id :reply ["value" ][id ]for id in ("first" ,"second" ,"third" )}, 113 {"first" :0 ,"second" :1 ,"third" :1 }, 114 ) 115self .channel .send ({"type" :"execute" ,"id" :"first" ,"code" :"unused" }) 116replayed = self .channel .wait_for ( 117lambda frame :frame ["type" ]== "done" and frame ["id" ]== "first" 118 ) 119self .assertEqual (replayed ["images" ], []) 120self .assertIn ("1 image(s) not kept for a replayed result" ,replayed ["output" ]) 121self .assertIn ("attached image/png" ,replayed ["value" ]) 122 123 124if __name__ == "__main__" : 125unittest .main ()