Skip to content

Commit f2cca64

Browse files
committed
add KernelRouter test
1 parent f242512 commit f2cca64

File tree

2 files changed

+320
-4
lines changed

2 files changed

+320
-4
lines changed
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
import type { Message as SwingsetMessage } from '@agoric/swingset-liveslots';
2+
import { describe, it, expect, vi, beforeEach } from 'vitest';
3+
import type { MockInstance } from 'vitest';
4+
5+
import { KernelQueue } from './KernelQueue.ts';
6+
import { KernelRouter } from './KernelRouter.ts';
7+
import type { KernelStore } from './store/index.ts';
8+
import type {
9+
RunQueueItem,
10+
RunQueueItemSend,
11+
RunQueueItemNotify,
12+
RunQueueItemGCAction,
13+
RunQueueItemBringOutYourDead,
14+
VatId,
15+
GCRunQueueType,
16+
} from './types.ts';
17+
import type { VatHandle } from './VatHandle.ts';
18+
19+
// Define Message type for tests that matches the required structure
20+
type Message = {
21+
methargs: { body: string; slots: string[] };
22+
result: string | null;
23+
};
24+
25+
describe('KernelRouter', () => {
26+
// Mock dependencies
27+
let kernelStore: KernelStore;
28+
let kernelQueue: KernelQueue;
29+
let getVat: (vatId: VatId) => VatHandle;
30+
let vatHandle: VatHandle;
31+
let kernelRouter: KernelRouter;
32+
33+
beforeEach(() => {
34+
// Mock VatHandle
35+
vatHandle = {
36+
deliverMessage: vi.fn().mockResolvedValue(undefined),
37+
deliverNotify: vi.fn().mockResolvedValue(undefined),
38+
deliverDropExports: vi.fn().mockResolvedValue(undefined),
39+
deliverRetireExports: vi.fn().mockResolvedValue(undefined),
40+
deliverRetireImports: vi.fn().mockResolvedValue(undefined),
41+
deliverBringOutYourDead: vi.fn().mockResolvedValue(undefined),
42+
} as unknown as VatHandle;
43+
44+
// Mock getVat function
45+
getVat = vi.fn().mockReturnValue(vatHandle);
46+
47+
// Mock KernelStore
48+
kernelStore = {
49+
getOwner: vi.fn(),
50+
getKernelPromise: vi.fn(),
51+
decrementRefCount: vi.fn(),
52+
setPromiseDecider: vi.fn(),
53+
translateRefKtoV: vi.fn(
54+
(_vatId: string, kref: string) => `translated-${kref}`,
55+
) as unknown as MockInstance,
56+
translateMessageKtoV: vi.fn(
57+
(_vatId: string, message: SwingsetMessage) =>
58+
message as unknown as SwingsetMessage,
59+
) as unknown as MockInstance,
60+
enqueuePromiseMessage: vi.fn(),
61+
erefToKref: vi.fn() as unknown as MockInstance,
62+
krefToEref: vi.fn() as unknown as MockInstance,
63+
getKpidsToRetire: vi.fn().mockReturnValue([]),
64+
translateCapDataKtoV: vi.fn(),
65+
krefsToExistingErefs: vi.fn((_vatId: string, krefs: string[]) =>
66+
krefs.map((kref: string) => `translated-${kref}`),
67+
) as unknown as MockInstance,
68+
} as unknown as KernelStore;
69+
70+
// Mock KernelQueue
71+
kernelQueue = {
72+
resolvePromises: vi.fn(),
73+
} as unknown as KernelQueue;
74+
75+
// Create the router to test
76+
kernelRouter = new KernelRouter(kernelStore, kernelQueue, getVat);
77+
});
78+
79+
describe('deliver', () => {
80+
describe('send', () => {
81+
it('delivers a send message to a vat with an object target', async () => {
82+
// Setup the kernel store to return an owner for the target
83+
const vatId = 'v1';
84+
const target = 'ko123';
85+
(kernelStore.getOwner as unknown as MockInstance).mockReturnValueOnce(
86+
vatId,
87+
);
88+
(kernelStore.erefToKref as unknown as MockInstance).mockReturnValueOnce(
89+
'not-the-target',
90+
);
91+
// Create a send message
92+
const message: Message = {
93+
methargs: { body: 'method args', slots: ['slot1', 'slot2'] },
94+
result: 'kp1',
95+
};
96+
const sendItem: RunQueueItemSend = {
97+
type: 'send',
98+
target,
99+
message: message as unknown as SwingsetMessage,
100+
};
101+
await kernelRouter.deliver(sendItem);
102+
// Verify the message was delivered to the vat
103+
expect(getVat).toHaveBeenCalledWith(vatId);
104+
expect(vatHandle.deliverMessage).toHaveBeenCalledWith(
105+
`translated-${target}`,
106+
message,
107+
);
108+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
109+
'slot1',
110+
'deliver|send|slot',
111+
);
112+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
113+
'slot2',
114+
'deliver|send|slot',
115+
);
116+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
117+
target,
118+
'deliver|send|target',
119+
);
120+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
121+
'kp1',
122+
'deliver|send|result',
123+
);
124+
});
125+
126+
it('splats a message when target has no owner', async () => {
127+
// Setup the kernel store to return no owner for the target
128+
(kernelStore.getOwner as unknown as MockInstance).mockReturnValueOnce(
129+
null,
130+
);
131+
132+
// Create a send message
133+
const target = 'ko123';
134+
const message: Message = {
135+
methargs: { body: 'method args', slots: ['slot1', 'slot2'] },
136+
result: 'kp1',
137+
};
138+
const sendItem: RunQueueItemSend = {
139+
type: 'send',
140+
target,
141+
message: message as unknown as SwingsetMessage,
142+
};
143+
await kernelRouter.deliver(sendItem);
144+
// Verify the message was not delivered to any vat and resources were cleaned up
145+
expect(getVat).not.toHaveBeenCalled();
146+
expect(vatHandle.deliverMessage).not.toHaveBeenCalled();
147+
// Verify refcounts were decremented
148+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
149+
target,
150+
'deliver|splat|target',
151+
);
152+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
153+
'slot1',
154+
'deliver|splat|slot',
155+
);
156+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
157+
'slot2',
158+
'deliver|splat|slot',
159+
);
160+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
161+
'kp1',
162+
'deliver|splat|result',
163+
);
164+
// Verify the promise was rejected with 'no vat'
165+
expect(kernelQueue.resolvePromises).toHaveBeenCalledWith(
166+
undefined,
167+
expect.arrayContaining([
168+
expect.arrayContaining(['kp1', true, expect.anything()]),
169+
]),
170+
);
171+
});
172+
173+
it('enqueues a message on an unresolved promise', async () => {
174+
// Setup a promise reference and unresolved promise in the kernel store
175+
const target = 'kp123';
176+
(
177+
kernelStore.getKernelPromise as unknown as MockInstance
178+
).mockReturnValueOnce({
179+
state: 'unresolved',
180+
});
181+
// Create a send message
182+
const message: Message = {
183+
methargs: { body: 'method args', slots: [] },
184+
result: null,
185+
};
186+
const sendItem: RunQueueItemSend = {
187+
type: 'send',
188+
target,
189+
message: message as unknown as SwingsetMessage,
190+
};
191+
await kernelRouter.deliver(sendItem);
192+
// Verify the message was enqueued on the promise
193+
expect(kernelStore.enqueuePromiseMessage).toHaveBeenCalledWith(
194+
target,
195+
message,
196+
);
197+
expect(getVat).not.toHaveBeenCalled();
198+
expect(vatHandle.deliverMessage).not.toHaveBeenCalled();
199+
});
200+
});
201+
202+
describe('notify', () => {
203+
it('delivers a notify to a vat', async () => {
204+
const vatId = 'v1';
205+
const kpid = 'kp123';
206+
const notifyItem: RunQueueItemNotify = {
207+
type: 'notify',
208+
vatId,
209+
kpid,
210+
};
211+
// Mock a resolved promise
212+
(
213+
kernelStore.getKernelPromise as unknown as MockInstance
214+
).mockReturnValueOnce({
215+
state: 'fulfilled',
216+
value: { body: 'resolved value', slots: [] },
217+
});
218+
// Mock that this promise is in the vat's clist
219+
(kernelStore.krefToEref as unknown as MockInstance).mockReturnValueOnce(
220+
'p+123',
221+
);
222+
// Mock that there's a promise to retire
223+
(
224+
kernelStore.getKpidsToRetire as unknown as MockInstance
225+
).mockReturnValueOnce([kpid]);
226+
// Mock the getKernelPromise for the target promise
227+
(
228+
kernelStore.getKernelPromise as unknown as MockInstance
229+
).mockReturnValueOnce({
230+
state: 'fulfilled',
231+
value: { body: 'target promise value', slots: [] },
232+
});
233+
// Deliver the notify
234+
await kernelRouter.deliver(notifyItem);
235+
// Verify the notification was delivered to the vat
236+
expect(getVat).toHaveBeenCalledWith(vatId);
237+
expect(vatHandle.deliverNotify).toHaveBeenCalledWith(expect.any(Array));
238+
expect(kernelStore.decrementRefCount).toHaveBeenCalledWith(
239+
kpid,
240+
'deliver|notify',
241+
);
242+
});
243+
244+
it('does nothing if the promise is not in vat clist', async () => {
245+
const vatId = 'v1';
246+
const kpid = 'kp123';
247+
const notifyItem: RunQueueItemNotify = {
248+
type: 'notify',
249+
vatId,
250+
kpid,
251+
};
252+
// Mock a resolved promise
253+
(
254+
kernelStore.getKernelPromise as unknown as MockInstance
255+
).mockReturnValueOnce({
256+
state: 'fulfilled',
257+
value: { body: 'resolved value', slots: [] },
258+
});
259+
// Mock that this promise is NOT in the vat's clist
260+
(kernelStore.krefToEref as unknown as MockInstance).mockReturnValueOnce(
261+
null,
262+
);
263+
// Deliver the notify
264+
await kernelRouter.deliver(notifyItem);
265+
// Verify no notification was delivered to the vat
266+
expect(vatHandle.deliverNotify).not.toHaveBeenCalled();
267+
});
268+
});
269+
270+
describe('gc actions', () => {
271+
it.each([
272+
['dropExports', 'deliverDropExports'],
273+
['retireExports', 'deliverRetireExports'],
274+
['retireImports', 'deliverRetireImports'],
275+
])('delivers %s to a vat', async (actionType, deliverMethod) => {
276+
const vatId = 'v1';
277+
const krefs = ['ko1', 'ko2'];
278+
const gcAction: RunQueueItemGCAction = {
279+
type: actionType as GCRunQueueType,
280+
vatId,
281+
krefs,
282+
};
283+
// Deliver the GC action
284+
await kernelRouter.deliver(gcAction);
285+
// Verify the action was delivered to the vat
286+
expect(getVat).toHaveBeenCalledWith(vatId);
287+
expect(
288+
vatHandle[deliverMethod as keyof VatHandle],
289+
).toHaveBeenCalledWith(krefs.map((kref) => `translated-${kref}`));
290+
});
291+
});
292+
293+
describe('bringOutYourDead', () => {
294+
it('delivers bringOutYourDead to a vat', async () => {
295+
const vatId = 'v1';
296+
const bringOutYourDeadItem: RunQueueItemBringOutYourDead = {
297+
type: 'bringOutYourDead',
298+
vatId,
299+
};
300+
// Deliver the bringOutYourDead action
301+
await kernelRouter.deliver(bringOutYourDeadItem);
302+
// Verify the action was delivered to the vat
303+
expect(getVat).toHaveBeenCalledWith(vatId);
304+
expect(vatHandle.deliverBringOutYourDead).toHaveBeenCalled();
305+
});
306+
});
307+
308+
it('throws on unknown run queue item type', async () => {
309+
// @ts-expect-error - deliberately using an invalid type
310+
const invalidItem: RunQueueItem = { type: 'invalid' };
311+
await expect(kernelRouter.deliver(invalidItem)).rejects.toThrow(
312+
'unknown run queue item type',
313+
);
314+
});
315+
});
316+
});

vitest.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ export default defineConfig({
8888
lines: 78.25,
8989
},
9090
'packages/kernel/**': {
91-
statements: 86.23,
92-
functions: 91.8,
93-
branches: 72.04,
94-
lines: 86.19,
91+
statements: 87.8,
92+
functions: 92.21,
93+
branches: 74.01,
94+
lines: 87.76,
9595
},
9696
'packages/nodejs/**': {
9797
statements: 72.91,

0 commit comments

Comments
 (0)