okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
1c00b60
main
1"""A restarted kernel gets back its helpers, not only its variables. 2 3Saved state is written when an idle kernel is released and read when the next 4turn opens a new one, so these scenarios run through the real daemon: the 5restore path lives in the kernel and the notice in the daemon. 6""" 7 8import os 9import pickle 10import unittest 11 12from harness import latest_user ,wait_until 13from harness import Albedo ,Provider ,exclusive ,python ,text 14 15IDLE_SECONDS = 1 16BIG_DEFINITION = "def big():\n return '" + "x" * 40000 + "'\n" 17 18DEFINE = ( 19"import json\n" 20"import os.path\n" 21"import xml.dom\n" 22"def helper(x):\n return x * 2\n" 23"class Box:\n def __init__(self, v):\n self.v = v\n" 24"answer = 7\n" 25"def rebound():\n return 1\n" 26"if answer:\n rebound = 5\n" + BIG_DEFINITION + "blob = bytes(9 * 1024 * 1024)\n" 27) 28FAIL = ( 29"def survivor():\n return 'alive'\n" 30"pass\n" 31"raise RuntimeError('boom')\n" 32"def never_ran():\n return 'unreachable'\n" 33) 34RECALL = ( 35"print('RESULT', helper(21), Box(3).v, json.dumps([1]), answer, rebound,\n" 36" survivor(), os.path.basename('a/b'), xml.dom.__name__,\n" 37" 'big' in globals(), 'blob' in globals(), 'never_ran' in globals())" 38) 39LEGACY = "print('LEGACY', legacy)" 40 41 42# exclusive: requires the daemon-wide idle-kernel timing knob 43@exclusive 44class KernelStateTests (unittest .TestCase ): 45def setUp (self ): 46cells = {"define" :DEFINE ,"fail" :FAIL ,"recall" :RECALL ,"legacy" :LEGACY } 47 48def script (request ): 49if request ["input" ][- 1 ].get ("role" )== "user" : 50prompt = latest_user (request ).split ("<system-note>" )[0 ] 51if prompt in cells : 52return python (cells [prompt ]) 53return text ("ok" ) 54 55self .provider = Provider (script ) 56self .addCleanup (self .provider .close ) 57self .app = Albedo ( 58self .provider , 59protocol = "responses" , 60prepare = lambda app :app .env .update (ALBEDO_IDLE_SECONDS = str (IDLE_SECONDS )), 61 ) 62self .app .__enter__ () 63self .addCleanup (self .app .__exit__ ,None ,None ,None ) 64 65def state (self ,session ): 66return self .app .home / "kernels" / f" { session } .state" 67 68def turn (self ,session ,prompt ): 69self .app .prompt (session ,prompt ).close () 70self .app .idle (session ) 71 72def saved_after (self ,session ,since ): 73"""The idle kernel was released again: its state file is newer than `since`.""" 74wait_until ( 75lambda : ( 76self .state (session ).exists () 77and self .state (session ).stat ().st_mtime_ns > since 78 ), 7915 , 80"the idle kernel was not saved" , 81 ) 82 83def outputs (self ): 84return "" .join ( 85item .get ("output" ,"" ) 86for item in self .provider .requests [- 1 ]["request" ]["input" ] 87 ) 88 89def test_helpers_classes_imports_and_failed_cells_survive_a_restart (self ): 90session = self .app .session () 91self .turn (session ,"define" ) 92self .saved_after (session ,0 ) 93saved = self .state (session ).stat ().st_mtime_ns 94self .turn (session ,"fail" ) 95self .saved_after (session ,saved ) 96self .turn (session ,"recall" ) 97output = self .outputs () 98self .assertIn ("RESULT 42 3 [1] 7 5 alive b xml.dom False False False" ,output ) 99notice = latest_user (self .provider .requests [- 2 ]["request" ]) 100self .assertIn ("re-run from source" ,notice ) 101self .assertIn ("helper" ,notice ) 102 103def test_a_state_file_in_the_old_format_still_restores_its_variables (self ): 104session = self .app .session () 105self .turn (session ,"define" ) 106self .saved_after (session ,0 ) 107legacy = {"cwd" :os .getcwd (),"names" : {"legacy" :pickle .dumps (5 )}} 108self .state (session ).write_bytes (pickle .dumps (legacy )) 109self .turn (session ,"legacy" ) 110self .assertIn ("LEGACY 5" ,self .outputs ())