1+ import test from 'node:test' ;
2+ import assert from 'node:assert/strict' ;
3+ import * as core from '../mutex_core.js' ;
4+ import { stepN , lastTrace } from './helpers.mjs' ;
5+
6+ test ( 'TokenRing edge branches: no alive processes, unknown/crashed holder, token remains' , ( ) => {
7+ const model = core . makeModel ( 2 , 'TokenRing' ) ;
8+
9+ core . crashProcess ( model , 'P2' ) ;
10+ let r = core . stepOnce ( model ) ;
11+ assert . equal ( r . type , 'token_pass' ) ;
12+ assert . match ( lastTrace ( model ) , / T o k e n r e m a i n s a t P 1 / ) ;
13+
14+ model . token . holder = 'PX' ;
15+ r = core . stepOnce ( model ) ;
16+ assert . equal ( r . reason , 'unknown_holder' ) ;
17+
18+ model . token . holder = 'P2' ;
19+ model . token . lost = false ;
20+ r = core . stepOnce ( model ) ;
21+ assert . equal ( r . reason , 'token_lost' ) ;
22+
23+ core . crashProcess ( model , 'P1' ) ;
24+ const regen = core . regenerateToken ( model ) ;
25+ assert . equal ( regen . reason , 'no_alive_processes' ) ;
26+
27+ model . ring = [ ] ;
28+ assert . equal ( core . ringNextAlive ( model , 'P1' ) , 'P1' ) ;
29+ } ) ;
30+
31+ test ( 'comparePid fallback path via RA script with non-numeric process IDs' , ( ) => {
32+ const demo = {
33+ type : 'MutexDemo' ,
34+ algorithm : 'RA' ,
35+ processes : [ 'PA' , 'PB' ] ,
36+ events : [
37+ { t : 0 , op : 'requestCS' , on : 'PA' } ,
38+ { t : 1 , op : 'requestCS' , on : 'PB' } ,
39+ { t : 2 , op : 'deliverNext' } ,
40+ { t : 3 , op : 'deliverNext' } ,
41+ { t : 4 , op : 'deliverNext' }
42+ ]
43+ } ;
44+
45+ const model = core . loadFromJsonObject ( demo ) ;
46+ stepN ( core , model , 5 ) ;
47+ assert . ok ( model . trace . some ( e => / e n t e r s t h e c r i t i c a l s e c t i o n / . test ( e . text ) ) ) ;
48+ } ) ;
49+
50+ test ( 'RA edge branches: crashed receiver drop and unsupported message type' , ( ) => {
51+ const model = core . makeModel ( 3 , 'RA' ) ;
52+
53+ core . requestCS ( model , 'P1' ) ;
54+ core . crashProcess ( model , 'P1' ) ;
55+
56+ model . network . queue . unshift ( { id : 999 , type : 'REPLY' , from : 'P2' , to : 'P1' , ts : 1 } ) ;
57+ let r = core . stepOnce ( model ) ;
58+ assert . equal ( r . type , 'message_dropped' ) ;
59+ assert . match ( lastTrace ( model ) , / M e s s a g e d r o p p e d : R E P L Y # 9 9 9 P 2 - > P 1 \( r e c e i v e r c r a s h e d \) / ) ;
60+
61+ const model2 = core . makeModel ( 2 , 'RA' ) ;
62+ model2 . network . queue . push ( { id : 1 , type : 'BOGUS' , from : 'P1' , to : 'P2' , ts : 1 } ) ;
63+ r = core . stepOnce ( model2 ) ;
64+ assert . equal ( r . type , 'message_delivered' ) ;
65+ assert . match ( lastTrace ( model2 ) , / U n s u p p o r t e d m e s s a g e t y p e / ) ;
66+ } ) ;
67+
68+ test ( 'script bad-event branches for TokenRing and RA are covered' , ( ) => {
69+ const trDemo = {
70+ type : 'MutexDemo' ,
71+ algorithm : 'TokenRing' ,
72+ processes : [ 'P1' , 'P2' ] ,
73+ events : [
74+ { } ,
75+ { t : 1 , op : 'holdToken' } ,
76+ { t : 2 , op : 'passToken' } ,
77+ { t : 3 , op : 'requestCS' } ,
78+ { t : 4 , op : 'requestCS' , on : 'PX' } ,
79+ { t : 5 , op : 'releaseCS' } ,
80+ { t : 6 , op : 'releaseCS' , on : 'PX' } ,
81+ { t : 7 , op : 'crash' } ,
82+ { t : 8 , op : 'recover' }
83+ ]
84+ } ;
85+ const tr = core . loadFromJsonObject ( trDemo ) ;
86+ stepN ( core , tr , trDemo . events . length ) ;
87+ const trTrace = tr . trace . map ( e => e . text ) . join ( '\n' ) ;
88+ assert . match (
89+ trTrace ,
90+ / m i s s i n g o p | h o l d T o k e n s k i p p e d | p a s s T o k e n s k i p p e d | r e q u e s t C S s k i p p e d | r e l e a s e C S s k i p p e d | c r a s h s k i p p e d | r e c o v e r s k i p p e d /
91+ ) ;
92+
93+ const raDemo = {
94+ type : 'MutexDemo' ,
95+ algorithm : 'RA' ,
96+ processes : [ 'P1' , 'P2' ] ,
97+ events : [
98+ { } ,
99+ { t : 1 , op : 'requestCS' } ,
100+ { t : 2 , op : 'releaseCS' } ,
101+ { t : 3 , op : 'crash' } ,
102+ { t : 4 , op : 'recover' }
103+ ]
104+ } ;
105+ const ra = core . loadFromJsonObject ( raDemo ) ;
106+ stepN ( core , ra , raDemo . events . length ) ;
107+ const raTrace = ra . trace . map ( e => e . text ) . join ( '\n' ) ;
108+ assert . match (
109+ raTrace ,
110+ / m i s s i n g o p | r e q u e s t C S s k i p p e d | r e l e a s e C S s k i p p e d | c r a s h s k i p p e d | r e c o v e r s k i p p e d /
111+ ) ;
112+ } ) ;
113+
114+ test ( 'exportStateObject default branch and load MutexState defaults/optional branches' , ( ) => {
115+ const weird = { algorithm : 'Other' , mode : 'interactive' , processes : [ ] } ;
116+ const obj = core . exportStateObject ( weird ) ;
117+ assert . equal ( obj . type , 'MutexState' ) ;
118+ assert . equal ( obj . algorithm , 'Other' ) ;
119+ assert . equal ( obj . mode , 'interactive' ) ;
120+
121+ const trState = {
122+ type : 'MutexState' ,
123+ algorithm : 'TokenRing' ,
124+ ring : [ 'P1' , 'P2' ] ,
125+ token : { holder : 'P2' , lost : true }
126+ } ;
127+ const tr = core . loadFromJsonObject ( trState ) ;
128+ assert . equal ( tr . token . holder , 'P2' ) ;
129+ assert . equal ( tr . token . lost , true ) ;
130+
131+ const raState = {
132+ type : 'MutexState' ,
133+ algorithm : 'RA' ,
134+ ring : [ 'P1' , 'P2' ] ,
135+ network : { nextMsgId : 7 , dropNextSend : true , queue : [ ] }
136+ } ;
137+ const ra = core . loadFromJsonObject ( raState ) ;
138+ assert . equal ( ra . network . nextMsgId , 7 ) ;
139+ assert . equal ( ra . network . dropNextSend , true ) ;
140+ } ) ;
0 commit comments