okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
57a82be
main
1"""The kernel half of the session layer, exercised frame by frame. 2 3Sequence, acknowledgement, and replay rules decide whether a reconnect loses 4or repeats a message, and E2E cannot choose where a connection drops. These 5tests drive albedo_link directly: the outbox's coalescing and bounds, replayed 6daemon frames, token checks, newest-attach-wins, and resending what the daemon 7never acknowledged. 8""" 9 10from pathlib import Path 11import os 12import shutil 13import socket 14import sys 15import tempfile 16import threading 17import unittest 18from unittest .mock import patch 19 20sys .path .insert (0 ,str (Path (__file__ ).resolve ().parents [2 ]/ "priv" / "python" )) 21import albedo_link # noqa: E402 22 23 24class OutboxTest (unittest .TestCase ): 25def test_ack_drops_everything_up_to_it (self ): 26outbox = albedo_link .Outbox () 27for n in range (4 ): 28outbox .add ({"type" :"call" ,"id" :str (n )}) 29outbox .ack (2 ) 30self .assertEqual ([seq for seq ,_ in outbox .pending ()], [3 ,4 ]) 31 32def test_mirrors_and_traces_keep_only_their_newest_copy (self ): 33outbox = albedo_link .Outbox () 34outbox .add ({"type" :"mirror" ,"handle" :"a" ,"tail" :"1" }) 35outbox .add ({"type" :"mirror" ,"handle" :"b" ,"tail" :"1" }) 36outbox .add ({"type" :"done" ,"id" :"c1" }) 37outbox .add ({"type" :"mirror" ,"handle" :"a" ,"tail" :"2" }) 38frames = [data for _ ,data in outbox .pending ()] 39self .assertEqual (len (frames ),3 ) 40self .assertIn (b'"tail":"2"' ,frames [- 1 ]) 41self .assertNotIn (b'"handle":"a","tail":"1"' ,b"" .join (frames )) 42 43def test_the_bound_sheds_coalescing_frames_before_deliveries (self ): 44outbox = albedo_link .Outbox (frames = 3 ) 45outbox .add ({"type" :"done" ,"id" :"c1" }) 46outbox .add ({"type" :"mirror" ,"handle" :"a" }) 47outbox .add ({"type" :"call" ,"id" :"x" }) 48outbox .add ({"type" :"job" ,"id" :"j" }) 49kinds = [data for _ ,data in outbox .pending ()] 50self .assertEqual (len (kinds ),3 ) 51self .assertFalse (any (b"mirror" in data for data in kinds )) 52self .assertEqual (outbox .dropped ,0 ) 53outbox .add ({"type" :"done" ,"id" :"c2" }) 54self .assertEqual (outbox .dropped ,1 ) 55self .assertNotIn (b'"c1"' ,b"" .join (data for _ ,data in outbox .pending ())) 56 57def test_the_bound_sheds_the_oldest_coalescing_frame_first (self ): 58outbox = albedo_link .Outbox (frames = 3 ) 59outbox .add ({"type" :"mirror" ,"handle" :"a" }) 60outbox .add ({"type" :"mirror" ,"handle" :"b" }) 61outbox .add ({"type" :"done" ,"id" :"c1" }) 62outbox .add ({"type" :"mirror" ,"handle" :"c" }) 63frames = b"" .join (data for _ ,data in outbox .pending ()) 64self .assertNotIn (b'"handle":"a"' ,frames ) 65self .assertIn (b'"handle":"b"' ,frames ) 66self .assertIn (b'"handle":"c"' ,frames ) 67self .assertEqual (outbox .dropped ,0 ) 68 69def test_a_replayed_daemon_frame_is_not_applied_twice (self ): 70inbound = albedo_link .Inbound () 71self .assertEqual ( 72 [inbound .accept (seq )for seq in (1 ,2 ,2 ,1 ,3 )], 73 [True ,True ,False ,False ,True ], 74 ) 75 76def test_job_book_follows_started_and_proven_gone_jobs (self ): 77book = albedo_link .JobBook () 78book .observe ({"type" :"job_start" ,"id" :"a" ,"pgid" :4242 }) 79book .observe ({"type" :"job_start" ,"id" :"b" ,"pgid" :4243 }) 80book .observe ({"type" :"job" ,"id" :"a" ,"cleanup" : {"gone" :False }}) 81book .observe ({"type" :"job" ,"id" :"b" ,"cleanup" : {"gone" :True }}) 82book .observe ({"type" :"jobs" ,"live" :2 }) 83self .assertEqual (sorted (book .started ), ["a" ]) 84self .assertEqual (book .live (),3 ) 85 86def test_job_book_keeps_the_remote_jobs_a_frame_lists (self ): 87book = albedo_link .JobBook () 88item = {"id" :"r" ,"command" :"sleep 9" ,"service" :False ,"started" :1 } 89book .observe ({"type" :"jobs" ,"live" :1 ,"awaited" :1 ,"items" : [item ]}) 90self .assertEqual (book .external_jobs , [item ]) 91book .observe ({"type" :"jobs" ,"live" :0 ,"awaited" :0 }) 92self .assertEqual (book .external_jobs , []) 93 94 95class SocketLinkTest (unittest .TestCase ): 96def setUp (self ): 97self .run_dir = tempfile .mkdtemp (prefix = "link-" ) 98self .received = [] 99self .link = albedo_link .SocketLink ( 100self .run_dir , 101"secret" , 102lambda : {"pid" :os .getpid ()}, 103bundle = "bundle" , 104grace = 60 , 105 ) 106threading .Thread (target = self .serve ,daemon = True ).start () 107 108def serve (self ): 109try : 110self .link .serve (self .received .append ) 111except OSError : 112pass # the listener closed in tearDown 113 114def tearDown (self ): 115self .link .listener .close () 116shutil .rmtree (self .run_dir ,ignore_errors = True ) 117 118def attach (self ,token = "secret" ,ack = 0 ): 119connection = socket .socket (socket .AF_UNIX ,socket .SOCK_STREAM ) 120connection .settimeout (5 ) 121connection .connect (os .path .join (self .run_dir ,albedo_link .SOCKET )) 122self .addCleanup (connection .close ) 123attach = {"attach" : {"token" :token ,"ack" :ack ,"grace" :30 }} 124albedo_link .write_frame (connection .send ,albedo_link .encode (attach )) 125return connection ,albedo_link .read_frame (connection .recv ) 126 127def frames (self ,connection ,count ): 128return [albedo_link .read_frame (connection .recv )for _ in range (count )] 129 130def test_a_wrong_token_is_refused (self ): 131_ ,answer = self .attach (token = "guess" ) 132self .assertEqual (answer , {"refused" :"wrong token" }) 133self .assertFalse (self .link .attached ()) 134 135def test_reattach_resends_only_what_was_not_acknowledged (self ): 136first ,hello = self .attach () 137self .assertEqual ((hello ["hello" ]["epoch" ],hello ["hello" ]["ack" ]), (1 ,0 )) 138self .assertEqual (self .link .grace ,30 ) 139for n in range (3 ): 140self .link .send ({"type" :"call" ,"id" :str (n )}) 141self .assertEqual ([f ["seq" ]for f in self .frames (first ,3 )], [1 ,2 ,3 ]) 142albedo_link .write_frame ( 143first .send , 144albedo_link .encode ( 145 {"seq" :1 ,"ack" :1 ,"frame" : {"type" :"release" ,"handle" :"h" }} 146 ), 147 ) 148self .assertEqual (albedo_link .read_frame (first .recv ), {"ack" :1 }) 149second ,hello = self .attach (ack = 2 ) 150# Newest attach wins: the first connection is cut off. 151self .assertEqual (first .recv (1 ),b"" ) 152self .assertEqual ((hello ["hello" ]["epoch" ],hello ["hello" ]["ack" ]), (2 ,1 )) 153 (resent ,)= self .frames (second ,1 ) 154self .assertEqual ((resent ["seq" ],resent ["frame" ]["id" ]), (3 ,"2" )) 155# The daemon replays seq 1 after its restart: it is not delivered again. 156replay = {"seq" :1 ,"ack" :3 ,"frame" : {"type" :"release" ,"handle" :"h" }} 157albedo_link .write_frame (second .send ,albedo_link .encode (replay )) 158self .assertEqual (albedo_link .read_frame (second .recv ), {"ack" :1 }) 159self .assertEqual (len (self .received ),1 ) 160self .assertEqual (self .link .outbox .pending (), []) 161 162def test_frames_sent_while_detached_wait_for_the_next_attach (self ): 163self .link .send ({"type" :"done" ,"id" :"c1" }) 164self .assertGreater (self .link .idle_for (),0 ) 165connection ,_ = self .attach () 166 (done ,)= self .frames (connection ,1 ) 167self .assertEqual (done ["frame" ], {"type" :"done" ,"id" :"c1" }) 168self .assertEqual (self .link .idle_for (),0 ) 169 170def test_job_stop_frame_dispatch (self ): 171connection ,_ = self .attach () 172frame = {"type" :"job_stop" ,"id" :"job-42" } 173albedo_link .write_frame ( 174connection .send , 175albedo_link .encode ({"seq" :1 ,"ack" :0 ,"frame" :frame }), 176 ) 177self .assertEqual (albedo_link .read_frame (connection .recv ), {"ack" :1 }) 178self .assertEqual (self .received , [frame ]) 179 180def test_cells_serve_job_stop_unknown_job_answers_not_found (self ): 181import albedo_cells as kernel 182 183sent = [] 184with patch .object (kernel ,"send" ,sent .append ): 185kernel .LOOP .run_until_complete (kernel .serve_job_stop ("missing-job" )) 186self .assertEqual ( 187sent , 188 [ 189 { 190"type" :"job_stop" , 191"id" :"missing-job" , 192"ok" :False , 193"error" :"job not found" , 194 } 195 ], 196 ) 197 198 199if __name__ == "__main__" : 200unittest .main ()