@@ -17,6 +17,26 @@ import type {
1717import { VatHandle } from './VatHandle.ts' ;
1818import { makeMapKernelDatabase } from '../test/storage.ts' ;
1919
20+ const mocks = vi . hoisted ( ( ) => {
21+ class KernelQueue {
22+ static lastInstance : KernelQueue ;
23+
24+ enqueueMessage = vi
25+ . fn ( )
26+ . mockResolvedValue ( { body : '{"result":"ok"}' , slots : [ ] } ) ;
27+
28+ run = vi . fn ( ) . mockResolvedValue ( undefined ) ;
29+
30+ constructor ( ) {
31+ ( this . constructor as typeof KernelQueue ) . lastInstance = this ;
32+ }
33+ }
34+ return { KernelQueue } ;
35+ } ) ;
36+ vi . mock ( './KernelQueue.ts' , ( ) => {
37+ return { KernelQueue : mocks . KernelQueue } ;
38+ } ) ;
39+
2040const makeMockVatConfig = ( ) : VatConfig => ( {
2141 sourceSpec : 'not-really-there.js' ,
2242} ) ;
@@ -67,15 +87,16 @@ describe('Kernel', () => {
6787 vatHandles = [ ] ;
6888 makeVatHandleMock = vi
6989 . spyOn ( VatHandle , 'make' )
70- . mockImplementation ( async ( ) => {
90+ . mockImplementation ( async ( { vatId , vatConfig } ) => {
7191 const vatHandle = {
92+ vatId,
93+ config : vatConfig ,
7294 init : vi . fn ( ) ,
7395 terminate : vi . fn ( ) ,
7496 handleMessage : vi . fn ( ) ,
7597 deliverMessage : vi . fn ( ) ,
7698 deliverNotify : vi . fn ( ) ,
7799 sendVatCommand : vi . fn ( ) ,
78- config : makeMockVatConfig ( ) ,
79100 } as unknown as VatHandle ;
80101 vatHandles . push ( vatHandle as Mocked < VatHandle > ) ;
81102 return vatHandle ;
@@ -84,6 +105,245 @@ describe('Kernel', () => {
84105 mockKernelDatabase = makeMapKernelDatabase ( ) ;
85106 } ) ;
86107
108+ describe ( 'constructor()' , ( ) => {
109+ it ( 'initializes the kernel without errors' , async ( ) => {
110+ expect (
111+ async ( ) =>
112+ await Kernel . make ( mockStream , mockWorkerService , mockKernelDatabase ) ,
113+ ) . not . toThrow ( ) ;
114+ } ) ;
115+
116+ it ( 'honors resetStorage option and clears persistent state' , async ( ) => {
117+ const db = makeMapKernelDatabase ( ) ;
118+ db . kernelKVStore . set ( 'foo' , 'bar' ) ;
119+ // Create with resetStorage should clear existing keys
120+ await Kernel . make ( mockStream , mockWorkerService , db , {
121+ resetStorage : true ,
122+ } ) ;
123+ expect ( db . kernelKVStore . get ( 'foo' ) ) . toBeUndefined ( ) ;
124+ } ) ;
125+ } ) ;
126+
127+ describe ( 'init()' , ( ) => {
128+ it ( 'initializes the kernel store' , async ( ) => {
129+ const kernel = await Kernel . make (
130+ mockStream ,
131+ mockWorkerService ,
132+ mockKernelDatabase ,
133+ ) ;
134+ await kernel . launchVat ( makeMockVatConfig ( ) ) ;
135+ expect ( kernel . getVatIds ( ) ) . toStrictEqual ( [ 'v1' ] ) ;
136+ } ) ;
137+
138+ it ( 'starts receiving messages' , async ( ) => {
139+ let drainHandler : ( ( message : JsonRpcRequest ) => Promise < void > ) | null =
140+ null ;
141+ const customMockStream = {
142+ drain : async ( handler : ( message : JsonRpcRequest ) => Promise < void > ) => {
143+ drainHandler = handler ;
144+ return Promise . resolve ( ) ;
145+ } ,
146+ write : vi . fn ( ) . mockResolvedValue ( undefined ) ,
147+ } as unknown as DuplexStream < JsonRpcRequest , JsonRpcResponse > ;
148+ await Kernel . make (
149+ customMockStream ,
150+ mockWorkerService ,
151+ mockKernelDatabase ,
152+ ) ;
153+ expect ( drainHandler ) . toBeInstanceOf ( Function ) ;
154+ } ) ;
155+
156+ it ( 'initializes and starts the kernel queue' , async ( ) => {
157+ await Kernel . make ( mockStream , mockWorkerService , mockKernelDatabase ) ;
158+ const queueInstance = mocks . KernelQueue . lastInstance ;
159+ expect ( queueInstance . run ) . toHaveBeenCalledTimes ( 1 ) ;
160+ } ) ;
161+
162+ it ( 'throws if the stream throws' , async ( ) => {
163+ const streamError = new Error ( 'Stream error' ) ;
164+ const throwingMockStream = {
165+ drain : ( ) => {
166+ throw streamError ;
167+ } ,
168+ write : vi . fn ( ) . mockResolvedValue ( undefined ) ,
169+ } as unknown as DuplexStream < JsonRpcRequest , JsonRpcResponse > ;
170+ await expect (
171+ Kernel . make ( throwingMockStream , mockWorkerService , mockKernelDatabase ) ,
172+ ) . rejects . toThrow ( 'Stream error' ) ;
173+ } ) ;
174+
175+ it ( 'recovers vats from persistent storage on startup' , async ( ) => {
176+ const db = makeMapKernelDatabase ( ) ;
177+ // Launch initial kernel and vat
178+ const kernel1 = await Kernel . make ( mockStream , mockWorkerService , db ) ;
179+ await kernel1 . launchVat ( makeMockVatConfig ( ) ) ;
180+ expect ( kernel1 . getVatIds ( ) ) . toStrictEqual ( [ 'v1' ] ) ;
181+ // Clear spies
182+ launchWorkerMock . mockClear ( ) ;
183+ makeVatHandleMock . mockClear ( ) ;
184+ // New kernel should recover existing vat
185+ const kernel2 = await Kernel . make ( mockStream , mockWorkerService , db ) ;
186+ expect ( launchWorkerMock ) . toHaveBeenCalledTimes ( 1 ) ;
187+ expect ( makeVatHandleMock ) . toHaveBeenCalledTimes ( 1 ) ;
188+ expect ( kernel2 . getVatIds ( ) ) . toStrictEqual ( [ 'v1' ] ) ;
189+ } ) ;
190+ } ) ;
191+
192+ describe ( 'reload()' , ( ) => {
193+ it ( 'should reload with current config when config exists' , async ( ) => {
194+ const kernel = await Kernel . make (
195+ mockStream ,
196+ mockWorkerService ,
197+ mockKernelDatabase ,
198+ ) ;
199+ kernel . clusterConfig = makeMockClusterConfig ( ) ;
200+ await kernel . launchVat ( makeMockVatConfig ( ) ) ;
201+ expect ( kernel . getVatIds ( ) ) . toStrictEqual ( [ 'v1' ] ) ;
202+ await kernel . reload ( ) ;
203+ // Verify the old vat was terminated
204+ expect ( vatHandles [ 0 ] ?. terminate ) . toHaveBeenCalledTimes ( 1 ) ;
205+ // Initial + reload
206+ expect ( launchWorkerMock ) . toHaveBeenCalledTimes ( 2 ) ;
207+ } ) ;
208+
209+ it ( 'should throw if no config exists' , async ( ) => {
210+ const kernel = await Kernel . make (
211+ mockStream ,
212+ mockWorkerService ,
213+ mockKernelDatabase ,
214+ ) ;
215+ await expect ( kernel . reload ( ) ) . rejects . toThrow ( 'no subcluster to reload' ) ;
216+ } ) ;
217+
218+ it ( 'should propagate errors from terminateAllVats' , async ( ) => {
219+ const kernel = await Kernel . make (
220+ mockStream ,
221+ mockWorkerService ,
222+ mockKernelDatabase ,
223+ ) ;
224+ kernel . clusterConfig = makeMockClusterConfig ( ) ;
225+ // Set up a vat that will throw during termination
226+ await kernel . launchVat ( makeMockVatConfig ( ) ) ;
227+ vatHandles [ 0 ] ?. terminate . mockRejectedValueOnce (
228+ new Error ( 'Termination failed' ) ,
229+ ) ;
230+ await expect ( kernel . reload ( ) ) . rejects . toThrow ( 'Termination failed' ) ;
231+ } ) ;
232+ } ) ;
233+
234+ describe ( 'queueMessage()' , ( ) => {
235+ it ( 'enqueues a message and returns the result' , async ( ) => {
236+ const kernel = await Kernel . make (
237+ mockStream ,
238+ mockWorkerService ,
239+ mockKernelDatabase ,
240+ ) ;
241+ await kernel . launchVat ( makeMockVatConfig ( ) ) ;
242+ const result = await kernel . queueMessage ( 'ko1' , 'hello' , [ ] ) ;
243+ expect ( result ) . toStrictEqual ( { body : '{"result":"ok"}' , slots : [ ] } ) ;
244+ } ) ;
245+ } ) ;
246+
247+ describe ( 'launchSubcluster()' , ( ) => {
248+ it ( 'launches a subcluster according to config' , async ( ) => {
249+ const kernel = await Kernel . make (
250+ mockStream ,
251+ mockWorkerService ,
252+ mockKernelDatabase ,
253+ ) ;
254+ const config = makeMockClusterConfig ( ) ;
255+ await kernel . launchSubcluster ( config ) ;
256+ expect ( launchWorkerMock ) . toHaveBeenCalled ( ) ;
257+ expect ( makeVatHandleMock ) . toHaveBeenCalled ( ) ;
258+ expect ( kernel . clusterConfig ) . toStrictEqual ( config ) ;
259+ } ) ;
260+
261+ it ( 'throws an error for invalid configs' , async ( ) => {
262+ const kernel = await Kernel . make (
263+ mockStream ,
264+ mockWorkerService ,
265+ mockKernelDatabase ,
266+ ) ;
267+ // @ts -expect-error Intentionally passing invalid config
268+ await expect ( kernel . launchSubcluster ( { } ) ) . rejects . toThrow (
269+ 'invalid cluster config' ,
270+ ) ;
271+ } ) ;
272+
273+ it ( 'throws an error when bootstrap vat name is invalid' , async ( ) => {
274+ const kernel = await Kernel . make (
275+ mockStream ,
276+ mockWorkerService ,
277+ mockKernelDatabase ,
278+ ) ;
279+ const invalidConfig = {
280+ bootstrap : 'nonexistent' ,
281+ vats : {
282+ alice : {
283+ sourceSpec : 'test.js' ,
284+ } ,
285+ } ,
286+ } ;
287+ await expect ( kernel . launchSubcluster ( invalidConfig ) ) . rejects . toThrow (
288+ 'invalid bootstrap vat name' ,
289+ ) ;
290+ } ) ;
291+
292+ it ( 'returns the bootstrap message result when bootstrap vat is specified' , async ( ) => {
293+ const kernel = await Kernel . make (
294+ mockStream ,
295+ mockWorkerService ,
296+ mockKernelDatabase ,
297+ ) ;
298+ const config = makeMockClusterConfig ( ) ;
299+ const result = await kernel . launchSubcluster ( config ) ;
300+ expect ( result ) . toStrictEqual ( { body : '{"result":"ok"}' , slots : [ ] } ) ;
301+ } ) ;
302+ } ) ;
303+
304+ describe ( 'clearStorage()' , ( ) => {
305+ it ( 'clears the kernel storage' , async ( ) => {
306+ const kernel = await Kernel . make (
307+ mockStream ,
308+ mockWorkerService ,
309+ mockKernelDatabase ,
310+ ) ;
311+ const clearSpy = vi . spyOn ( mockKernelDatabase , 'clear' ) ;
312+ await kernel . clearStorage ( ) ;
313+ expect ( clearSpy ) . toHaveBeenCalledOnce ( ) ;
314+ } ) ;
315+ } ) ;
316+
317+ describe ( 'getVats()' , ( ) => {
318+ it ( 'returns an empty array when no vats are added' , async ( ) => {
319+ const kernel = await Kernel . make (
320+ mockStream ,
321+ mockWorkerService ,
322+ mockKernelDatabase ,
323+ ) ;
324+ expect ( kernel . getVats ( ) ) . toStrictEqual ( [ ] ) ;
325+ } ) ;
326+
327+ it ( 'returns vat information after adding vats' , async ( ) => {
328+ const kernel = await Kernel . make (
329+ mockStream ,
330+ mockWorkerService ,
331+ mockKernelDatabase ,
332+ ) ;
333+ const config = makeMockVatConfig ( ) ;
334+ await kernel . launchVat ( config ) ;
335+ const vats = kernel . getVats ( ) ;
336+ expect ( vats ) . toHaveLength ( 1 ) ;
337+ console . log ( vats ) ;
338+ expect ( vats ) . toStrictEqual ( [
339+ {
340+ id : 'v1' ,
341+ config,
342+ } ,
343+ ] ) ;
344+ } ) ;
345+ } ) ;
346+
87347 describe ( 'getVatIds()' , ( ) => {
88348 it ( 'returns an empty array when no vats are added' , async ( ) => {
89349 const kernel = await Kernel . make (
@@ -289,59 +549,75 @@ describe('Kernel', () => {
289549 expect ( vatHandles [ 0 ] ?. terminate ) . toHaveBeenCalledOnce ( ) ;
290550 expect ( kernel . getVatIds ( ) ) . toStrictEqual ( [ ] ) ;
291551 } ) ;
292- } ) ;
293552
294- describe ( 'constructor()' , ( ) => {
295- it ( 'initializes the kernel without errors' , async ( ) => {
296- expect (
297- async ( ) =>
298- await Kernel . make ( mockStream , mockWorkerService , mockKernelDatabase ) ,
299- ) . not . toThrow ( ) ;
553+ it ( 'returns the existing VatHandle instance on restart' , async ( ) => {
554+ const kernel = await Kernel . make (
555+ mockStream ,
556+ mockWorkerService ,
557+ mockKernelDatabase ,
558+ ) ;
559+ await kernel . launchVat ( makeMockVatConfig ( ) ) ;
560+ const originalHandle = vatHandles [ 0 ] ;
561+ const returnedHandle = await kernel . restartVat ( 'v1' ) ;
562+ expect ( returnedHandle ) . toBe ( originalHandle ) ;
300563 } ) ;
301564 } ) ;
302565
303- describe ( 'init()' , ( ) => {
304- it . todo ( 'initializes the kernel store' ) ;
305- it . todo ( 'starts receiving messages' ) ;
306- it . todo ( 'throws if the stream throws' ) ;
307- } ) ;
308-
309- describe ( 'reload()' , ( ) => {
310- it ( 'should reload with current config when config exists' , async ( ) => {
566+ describe ( 'clusterConfig' , ( ) => {
567+ it ( 'gets and sets cluster configuration' , async ( ) => {
311568 const kernel = await Kernel . make (
312569 mockStream ,
313570 mockWorkerService ,
314571 mockKernelDatabase ,
315572 ) ;
316- kernel . clusterConfig = makeMockClusterConfig ( ) ;
317- await kernel . launchVat ( makeMockVatConfig ( ) ) ;
318- const launchSubclusterMock = vi
319- . spyOn ( kernel , 'launchSubcluster' )
320- . mockResolvedValueOnce ( undefined ) ;
321- await kernel . reload ( ) ;
322- expect ( vatHandles [ 0 ] ?. terminate ) . toHaveBeenCalledTimes ( 1 ) ;
323- expect ( launchSubclusterMock ) . toHaveBeenCalledOnce ( ) ;
573+ expect ( kernel . clusterConfig ) . toBeNull ( ) ;
574+ const config = makeMockClusterConfig ( ) ;
575+ kernel . clusterConfig = config ;
576+ expect ( kernel . clusterConfig ) . toStrictEqual ( config ) ;
324577 } ) ;
325578
326- it ( 'should throw if no config exists ' , async ( ) => {
579+ it ( 'throws an error when setting invalid config ' , async ( ) => {
327580 const kernel = await Kernel . make (
328581 mockStream ,
329582 mockWorkerService ,
330583 mockKernelDatabase ,
331584 ) ;
332- await expect ( kernel . reload ( ) ) . rejects . toThrow ( 'no subcluster to reload' ) ;
585+ expect ( ( ) => {
586+ // @ts -expect-error Intentionally setting invalid config
587+ kernel . clusterConfig = { invalid : true } ;
588+ } ) . toThrow ( 'invalid cluster config' ) ;
333589 } ) ;
590+ } ) ;
334591
335- it ( 'should propagate errors from terminateAllVats' , async ( ) => {
592+ describe ( 'reset()' , ( ) => {
593+ it ( 'terminates all vats and resets kernel state' , async ( ) => {
594+ const mockDb = makeMapKernelDatabase ( ) ;
595+ const clearSpy = vi . spyOn ( mockDb , 'clear' ) ;
596+ const kernel = await Kernel . make ( mockStream , mockWorkerService , mockDb ) ;
597+ await kernel . launchVat ( makeMockVatConfig ( ) ) ;
598+ await kernel . reset ( ) ;
599+ expect ( clearSpy ) . toHaveBeenCalled ( ) ;
600+ expect ( kernel . getVatIds ( ) ) . toHaveLength ( 0 ) ;
601+ } ) ;
602+ } ) ;
603+
604+ describe ( 'pinVatRoot and unpinVatRoot' , ( ) => {
605+ it ( 'pins and unpins a vat root correctly' , async ( ) => {
336606 const kernel = await Kernel . make (
337607 mockStream ,
338608 mockWorkerService ,
339609 mockKernelDatabase ,
340610 ) ;
341- kernel . clusterConfig = makeMockClusterConfig ( ) ;
342- const error = new Error ( 'Termination failed' ) ;
343- vi . spyOn ( kernel , 'terminateAllVats' ) . mockRejectedValueOnce ( error ) ;
344- await expect ( kernel . reload ( ) ) . rejects . toThrow ( error ) ;
611+ const config = makeMockVatConfig ( ) ;
612+ const rootRef = await kernel . launchVat ( config ) ;
613+ // Pinning existing vat root should return the kref
614+ expect ( kernel . pinVatRoot ( 'v1' ) ) . toBe ( rootRef ) ;
615+ // Pinning non-existent vat should throw
616+ expect ( ( ) => kernel . pinVatRoot ( 'v2' ) ) . toThrow ( VatNotFoundError ) ;
617+ // Unpinning existing vat root should succeed
618+ expect ( ( ) => kernel . unpinVatRoot ( 'v1' ) ) . not . toThrow ( ) ;
619+ // Unpinning non-existent vat should throw
620+ expect ( ( ) => kernel . unpinVatRoot ( 'v3' ) ) . toThrow ( VatNotFoundError ) ;
345621 } ) ;
346622 } ) ;
347623} ) ;
0 commit comments