okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
840828d
main
1//// Kernel ownership and timed-out execution state need nonflaky runtime-level probes. 2 3import albedo/daemon/conversation 4import albedo/daemon/session 5import albedo/daemon/store 6import albedo/harness/extension 7import albedo/harness/extension/selection 8import albedo/harness/extensions/python/kernel as python 9import albedo/harness/extensions/python/link 10import albedo/harness/runtime 11import albedo/harness/runtime/state as runtime_state 12import gleam/erlang/process 13import gleam/int 14import gleam/list 15import gleam/option .{ None , Some } 16import gleam/string 17import gleeunit/should 18import harness/session_fixture 19 20@external ( erlang , "albedo_runtime_test_support" , "kernel_memory" ) 21fn kernel_memory ( session : runtime . Session ) -> Int 22 23@external ( erlang , "albedo_runtime_test_support" , "temporary_workspace" ) 24fn temporary_workspace () -> String 25 26@external ( erlang , "albedo_runtime_test_support" , "cleanup_workspace" ) 27fn cleanup_workspace ( path : String ) -> Nil 28 29@external ( erlang , "albedo_copy_test_support" , "captured_words" ) 30fn captured_words ( term : a) -> Int 31 32/// Every http request and every turn's worker copies the runtime handle and 33/// a session's selection into a fresh process, so neither may carry the 34/// installed extensions: the handle shares them, a selection points at them. 35pub fn runtime_handle_and_selection_carry_no_installed_copy_test () -> Nil { 36let workspace = temporary_workspace () 37let assert Ok ( host ) = runtime . start ( ":memory:" ) 38session_fixture . initialise ( host ) 39session_fixture . create ( host , "handle" , workspace ) 40let assert Ok ( _ ) = runtime . open_session ( host , "handle" , workspace ) 41let baseline = captured_words ( Nil ) 42let installed = runtime . installed ( host ) 43let assert Ok ( enabled ) = runtime . global ( host ) 44// Measured while the runtime lives: its stop releases the shared value, 45// which lands a copy on every heap still holding it. 46let handle_words = captured_words ( host ) 47let installed_words = captured_words ( installed ) 48let enabled_words = captured_words ( enabled ) 49runtime . stop ( host ) 50cleanup_workspace ( workspace ) 51should . be_true ( list . length ( installed ) > 10 ) 52should . be_true ( list . length ( enabled ) > 10 ) 53// The handle: a subject, a store, and a shared reference. 54should . be_true ( handle_words < baseline + 256 ) 55installed_words |> should . equal ( baseline ) 56// A session's selection: one cons cell per enabled extension. 57should . be_true ( enabled_words < baseline + 256 ) 58} 59 60pub fn kernel_host_ownership_does_not_grow_with_the_swarm_test () -> Nil { 61let workspace = temporary_workspace () 62let assert Ok ( host ) = runtime . start_with_extensions ( ":memory:" , []) 63session_fixture . initialise ( host ) 64session_fixture . create ( host , "first" , workspace ) 65let assert Ok ( first ) = runtime . open_session ( host , "first" , workspace ) 66let baseline = kernel_memory ( first ) 67// A hundred dormant compositions, not a hundred OS processes. None belongs 68// in another kernel's RPC closure or in the worker that boots that kernel. 69list . repeat ( Nil , 100 ) 70|> list . index_map ( fn ( _ , n ) { 71let id = "dormant-" <> int . to_string ( n ) 72session_fixture . create ( host , id , workspace ) 73let assert Ok ( _ ) = runtime . peek_commands ( host , id , workspace ) 74let assert Ok ( observed ) = runtime . observe_loaded ( host , id ) 75observed . kernel |> should . equal ( None ) 76observed . phase |> should . equal ( "none" ) 77}) 78session_fixture . create ( host , "last" , workspace ) 79let assert Ok ( last ) = runtime . open_session ( host , "last" , workspace ) 80let opened = kernel_memory ( last ) 81let assert Ok ( Some ( refreshed )) = 82runtime . reload_desired ( host , "last" , workspace ) 83let rebound = kernel_memory ( refreshed ) 84runtime . stop ( host ) 85cleanup_workspace ( workspace ) 86// Allocator size classes may differ; sibling state must not accumulate. 87should . be_true ( opened <= baseline * 2 + 4096 ) 88should . be_true ( rebound <= baseline * 2 + 4096 ) 89} 90 91pub fn hard_timeout_requires_explicit_reset_test () -> Nil { 92let assert Ok ( host ) = runtime . start ( ":memory:" ) 93session_fixture . initialise ( host ) 94session_fixture . create ( host , "a" , "/tmp" ) 95let assert Ok ( session ) = runtime . open_session ( host , "a" , "/tmp" ) 96// SIGINT is ignored before the timed cell starts: in the same cell, a 97// deadline landing ahead of the signal.signal line under load is a 98// graceful interrupt instead. 99let assert Ok ( _ ) = 100runtime . execute ( 101host , 102session , 103"import signal\nsignal.signal(signal.SIGINT, signal.SIG_IGN)" , 10410_000 , 105) 106let assert Ok ( execution ) = 107runtime . execute ( host , session , "while True: pass" , 100 ) 108execution . result |> should . equal ( Error ( python . Lost )) 109runtime . open_session ( host , "a" , "/tmp" ) |> should . equal ( Error ( python . Lost )) 110runtime . reset_session ( host , "a" ) 111let assert Ok ( _ ) = runtime . open_session ( host , "a" , "/tmp" ) 112runtime . stop ( host ) 113} 114 115@external ( erlang , "albedo_runtime_test_support" , "catalog_does_not_block" ) 116fn catalog_does_not_block ( 117runtime : runtime . Runtime , 118home : String , 119id : String , 120) -> #( Bool , Bool ) 121 122pub fn catalog_store_wait_does_not_block_runtime_observations_test () -> Nil { 123let home = temporary_workspace () 124let assert Ok ( host ) = runtime . start ( ":memory:" ) 125session_fixture . initialise ( host ) 126session_fixture . create ( host , "catalog" , home ) 127let #( responsive , completed ) = catalog_does_not_block ( host , home , "catalog" ) 128runtime . stop ( host ) 129cleanup_workspace ( home ) 130responsive |> should . be_true 131completed |> should . be_true 132} 133 134@external ( erlang , "albedo_runtime_test_support" , "catalog_forget_while_blocked" ) 135fn catalog_forget_while_blocked ( 136runtime : runtime . Runtime , 137home : String , 138id : String , 139) -> Result ( runtime . CatalogObservation , String ) 140 141pub fn stale_catalog_retries_against_current_composition_test () -> Nil { 142let home = temporary_workspace () 143let assert Ok ( host ) = runtime . start_with_extensions ( ":memory:" , []) 144session_fixture . initialise ( host ) 145session_fixture . create ( host , "catalog" , home ) 146let assert Ok ( _ ) = runtime . peek_commands ( host , "catalog" , home ) 147let assert Ok ( initial ) = runtime . observe_catalog ( host , home , "catalog" ) 148should . be_true ( initial . loaded_revision != None ) 149let assert Ok ( current ) = catalog_forget_while_blocked ( host , home , "catalog" ) 150current . loaded_revision |> should . equal ( None ) 151runtime . loaded_sessions ( host ) |> should . equal ([]) 152let assert Ok ( fresh ) = runtime . observe_catalog ( host , home , "catalog" ) 153fresh . loaded_revision |> should . equal ( None ) 154runtime . stop ( host ) 155cleanup_workspace ( home ) 156} 157 158@external ( erlang , "albedo_runtime_test_support" , "hold_preparation" ) 159fn hold_preparation ( 160parent : process . Pid , 161workspace : String , 162) -> Result ( String , String ) 163 164@external ( erlang , "albedo_runtime_test_support" , "capture_during_preparation" ) 165fn capture_during_preparation ( 166host : runtime . Runtime , 167reader : session . Session , 168requests : List (#( String , String )), 169) -> #( Result ( session . Capture , String ), Bool ) 170 171pub fn blocked_preparations_leave_session_capture_responsive_test () -> Nil { 172let home = temporary_workspace () 173let parent = process . self () 174let held = 175extension . Extension ( 176name : "held-context" , 177description : "context loading controlled by the test's release barrier" , 178requires : [], 179plugins : [ 180extension . ContextPlugin ( fn ( workspace ) { 181hold_preparation ( parent , workspace ) 182}), 183], 184initialise : fn ( ledger ) { store . query ( ledger , link . apply ) }, 185) 186let assert Ok ( host ) = runtime . start_with_extensions ( ":memory:" , [ held ]) 187session_fixture . initialise ( host ) 188let requests = 189list . repeat ( Nil , 8 ) 190|> list . index_map ( fn ( _ , index ) { 191let id = "preparing-" <> int . to_string ( index ) 192let workspace = temporary_workspace () 193session_fixture . create ( host , id , workspace ) 194// Keep the blocking loader selected regardless of shared global defaults. 195let assert Ok ( _ ) = 196selection . record_selected ( 197runtime . ledger ( host ), 198id , 199selection . SetSession ( held . name , True ), 200[], 201[ held ], 202[ held ], 203) 204#( id , workspace ) 205}) 206session_fixture . create ( host , "reader" , home ) 207let assert Ok ( info ) = conversation . get ( runtime . ledger ( host ), "reader" ) 208let assert Ok ( reader ) = session . start ( host , info , home ) 209let #( outcome , completed ) = capture_during_preparation ( host , reader , requests ) 210session . close ( reader ) 211runtime . stop ( host ) 212list . each ( requests , fn ( request ) { cleanup_workspace ( request . 1 ) }) 213cleanup_workspace ( home ) 214let assert Ok ( captured ) = outcome 215captured . info . id |> should . equal ( "reader" ) 216completed |> should . be_true 217} 218 219/// SQLite publication failure can only be placed precisely with an injected 220/// trigger. An ordinary daemon request cannot target the post-shutdown gap. 221pub fn failed_publication_never_restores_a_dead_kernel_test () -> Nil { 222let workspace = temporary_workspace () 223let assert Ok ( host ) = 224runtime . start_with_extensions ( ":memory:" , [ 225extension . python_module ( "module" , "fixture" , "run" , "" , []), 226]) 227session_fixture . initialise ( host ) 228session_fixture . create ( host , "publication" , workspace ) 229let assert Ok ( old ) = runtime . open_session ( host , "publication" , workspace ) 230let ledger = runtime . ledger ( host ) 231let assert Ok ( _ ) = 232store . query ( ledger , fn ( db ) { 233store . exec ( 234db , 235"CREATE TRIGGER refuse_publication BEFORE INSERT ON kernel_links BEGIN SELECT RAISE(FAIL, 'publication refused'); END;" , 236) 237}) 238let assert runtime_state . ApplyFailed ( retained , reason ) = 239runtime . apply_change ( 240host , 241"publication" , 242workspace , 243selection . SetSession ( "module" , False ), 244) 245retained |> should . equal ( None ) 246runtime . alive ( old ) |> should . be_false 247string . contains ( reason , "publication refused" ) |> should . be_true 248let assert Ok ( loaded ) = runtime . observe_loaded ( host , "publication" ) 249loaded . loaded_revision |> should . equal ( None ) 250loaded . kernel |> should . equal ( None ) 251let assert Ok ( chosen ) = selection . overrides ( ledger , "publication" ) 252list . contains ( chosen , #( "module" , False )) |> should . be_true 253let assert Ok ( stages ) = link . stages ( ledger ) 254stages |> should . equal ([]) 255let assert Ok ( _ ) = 256store . query ( ledger , fn ( db ) { 257store . exec ( db , "DROP TRIGGER refuse_publication" ) 258}) 259let assert Ok ( fresh ) = runtime . open_session ( host , "publication" , workspace ) 260runtime . alive ( fresh ) |> should . be_true 261runtime . stop ( host ) 262cleanup_workspace ( workspace ) 263}