Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions packages/kernel-test/src/persistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ describe('persistent storage', { timeout: 20_000 }, () => {
};

it('maintains state across kernel restarts', async () => {
const database = await makeSQLKernelDatabase({ dbFilename: databasePath });
const database1 = await makeSQLKernelDatabase({ dbFilename: databasePath });
const kernel1 = await makeKernel(
database,
database1,
false,
logger.logger.subLogger({ tags: ['test'] }),
);
Expand All @@ -60,14 +60,16 @@ describe('persistent storage', { timeout: 20_000 }, () => {
expect(incrementResult1).toBe('Counter incremented to: 2');
await waitUntilQuiescent();
await kernel1.stop();
const database2 = await makeSQLKernelDatabase({ dbFilename: databasePath });
const kernel2 = await makeKernel(
database,
database2,
false,
logger.logger.subLogger({ tags: ['test'] }),
);
await new Promise((resolve) => setTimeout(resolve, 1000));
const resumeResult = await runResume(kernel2, v1Root);
expect(resumeResult).toBe('Counter incremented to: 3');
await kernel2.stop();
});

it('handles multiple vats with persistent state', async () => {
Expand All @@ -88,9 +90,9 @@ describe('persistent storage', { timeout: 20_000 }, () => {
},
},
};
const database = await makeSQLKernelDatabase({ dbFilename: databasePath });
const database1 = await makeSQLKernelDatabase({ dbFilename: databasePath });
const kernel1 = await makeKernel(
database,
database1,
false,
logger.logger.subLogger({ tags: ['test'] }),
);
Expand All @@ -101,14 +103,16 @@ describe('persistent storage', { timeout: 20_000 }, () => {
expect(workResult1).toBe('Work completed: Worker1(1), Worker2(1)');
await waitUntilQuiescent();
await kernel1.stop();
const database2 = await makeSQLKernelDatabase({ dbFilename: databasePath });
const kernel2 = await makeKernel(
database,
database2,
false,
logger.logger.subLogger({ tags: ['test'] }),
);
await new Promise((resolve) => setTimeout(resolve, 1000));
const workResult2 = await runResume(kernel2, v1Root);
expect(workResult2).toBe('Work completed: Worker1(2), Worker2(2)');
await kernel2.stop();
});

it('respects resetStorage flag when set to true', async () => {
Expand Down Expand Up @@ -148,10 +152,10 @@ describe('persistent storage', { timeout: 20_000 }, () => {
});

it('handles messages in queue after kernel restart', async () => {
const database = await makeSQLKernelDatabase({ dbFilename: databasePath });
const kernelStore = makeKernelStore(database);
const database1 = await makeSQLKernelDatabase({ dbFilename: databasePath });
const kernelStore1 = makeKernelStore(database1);
const kernel1 = await makeKernel(
database,
database1,
false,
logger.logger.subLogger({ tags: ['test'] }),
);
Expand All @@ -164,31 +168,34 @@ describe('persistent storage', { timeout: 20_000 }, () => {
const result1 = await kernel1.queueMessage(v1Root, 'resume', []);
expect(kunser(result1)).toBe('Counter incremented to: 2');
// Enqueue a send message into the database
kernelStore.kv.set('queue.run.head', '4');
kernelStore.kv.set('nextPromiseId', '4');
kernelStore.kv.set(`${v1Root}.refCount`, '3,3');
kernelStore.kv.set('queue.kp3.head', '1');
kernelStore.kv.set('queue.kp3.tail', '1');
kernelStore.kv.set('kp3.state', 'unresolved');
kernelStore.kv.set('kp3.subscribers', '[]');
kernelStore.kv.set('kp3.refCount', '2');
kernelStore.kv.set(
kernelStore1.kv.set('queue.run.head', '4');
kernelStore1.kv.set('nextPromiseId', '4');
kernelStore1.kv.set(`${v1Root}.refCount`, '3,3');
kernelStore1.kv.set('queue.kp3.head', '1');
kernelStore1.kv.set('queue.kp3.tail', '1');
kernelStore1.kv.set('kp3.state', 'unresolved');
kernelStore1.kv.set('kp3.subscribers', '[]');
kernelStore1.kv.set('kp3.refCount', '2');
kernelStore1.kv.set(
'queue.run.3',
`{"type":"send","target":"${v1Root}","message":{"methargs":{"body":"#[\\"resume\\",[]]","slots":[]},"result":"kp3"}}`,
);
await kernel1.stop();
// verify that the message is in the database
expect(kernelStore.kv.get('queue.run.3')).toBeDefined();
// Open a fresh connection to verify the message is in the database
const database2 = await makeSQLKernelDatabase({ dbFilename: databasePath });
const kernelStore2 = makeKernelStore(database2);
expect(kernelStore2.kv.get('queue.run.3')).toBeDefined();
// restart the kernel
const kernel2 = await makeKernel(
database,
database2,
false,
logger.logger.subLogger({ tags: ['test'] }),
);
// verify that the run queue is empty
expect(kernelStore.kv.get('queue.run.3')).toBeUndefined();
expect(kernelStore2.kv.get('queue.run.3')).toBeUndefined();
// verify that the message is processed and the counter is incremented
const result2 = await kernel2.queueMessage(v1Root, 'resume', []);
expect(kunser(result2)).toBe('Counter incremented to: 4');
await kernel2.stop();
});
});
211 changes: 129 additions & 82 deletions packages/kernel-test/src/remote-comms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import type {
RemoteCommsOptions,
} from '@metamask/ocap-kernel';
import { NodejsPlatformServices } from '@ocap/nodejs';
import { describe, it, expect, beforeEach } from 'vitest';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

import {
makeTestLogger,
Expand Down Expand Up @@ -278,6 +281,17 @@ describe('Remote Communications (Integration Tests)', () => {
);
});

afterEach(async () => {
await Promise.all([
kernel1.stop().catch(() => {
// already stopped inside the test
}),
kernel2.stop().catch(() => {
// already stopped inside the test
}),
]);
});

it('should initialize remote communications without errors', async () => {
const status1 = await kernel1.getStatus();
const status2 = await kernel2.getStatus();
Expand Down Expand Up @@ -365,87 +379,120 @@ describe('Remote Communications (Integration Tests)', () => {
});

it('remote relationships should survive kernel restart', async () => {
// Launch client vat on kernel1
const clientConfig = makeMaasClientConfig('client1', true);
let clientKernel = kernel1;
await runTestVats(clientKernel, clientConfig);
const clientRootRef = kernelStore1.getRootObject('v1') as KRef;

// Launch server vat on kernel2
const serverConfig = makeMaasServerConfig('server2', true);
let serverKernel = kernel2;
const serverResult = await runTestVats(serverKernel, serverConfig);

// The server's ocap URL is its bootstrap result
const serverURL = serverResult as string;

expect(typeof serverURL).toBe('string');
expect(serverURL).toMatch(/^ocap:/u);

// Configure the client with the server's URL
const setupResult = await clientKernel.queueMessage(
clientRootRef,
'setMaas',
[serverURL],
);
let response = kunser(setupResult);
expect(response).toBeDefined();
expect(response).toContain('MaaS service URL set');

// Tell the client to talk to the server
let expectedCount = 1;
const stepResult = await clientKernel.queueMessage(
clientRootRef,
'step',
[],
);
response = kunser(stepResult);
expect(response).toBeDefined();
expect(response).toContain(`next step: ${expectedCount} `);

// Kill the server and restart it
await serverKernel.stop();
serverKernel = await makeTestKernel(
'kernel2b',
kernelDatabase2,
directNetwork,
false,
'kernel2-peer',
'02',
);

// Tell the client to talk to the server a second time
expectedCount += 1;
const stepResult2 = await clientKernel.queueMessage(
clientRootRef,
'step',
[],
);
response = kunser(stepResult2);
expect(response).toBeDefined();
expect(response).toContain(`next step: ${expectedCount} `);

// Kill the client and restart it
await clientKernel.stop();
clientKernel = await makeTestKernel(
'kernel1b',
kernelDatabase1,
directNetwork,
false,
'kernel1-peer',
'01',
);

// Tell the client to talk to the server a third time
expectedCount += 1;
const stepResult3 = await clientKernel.queueMessage(
clientRootRef,
'step',
[],
);
response = kunser(stepResult3);
expect(response).toBeDefined();
expect(response).toContain(`next step: ${expectedCount} `);
// This test needs file-based databases for persistence across stop/restart.
// Stop the beforeEach kernels and replace them with file-backed ones.
const tempDir = await mkdtemp(join(tmpdir(), 'kernel-test-rc-'));
const dbFile1 = join(tempDir, 'k1.db');
const dbFile2 = join(tempDir, 'k2.db');
try {
await Promise.all([kernel1.stop(), kernel2.stop()]);

const initDb1 = await makeSQLKernelDatabase({ dbFilename: dbFile1 });
const localKernelStore1 = makeKernelStore(initDb1);
let clientKernel = await makeTestKernel(
'kernel1',
initDb1,
directNetwork,
true,
'kernel1-peer',
'01',
);

const initDb2 = await makeSQLKernelDatabase({ dbFilename: dbFile2 });
let serverKernel = await makeTestKernel(
'kernel2',
initDb2,
directNetwork,
true,
'kernel2-peer',
'02',
);

// Launch client vat on kernel1
const clientConfig = makeMaasClientConfig('client1', true);
await runTestVats(clientKernel, clientConfig);
const clientRootRef = localKernelStore1.getRootObject('v1') as KRef;

// Launch server vat on kernel2
const serverConfig = makeMaasServerConfig('server2', true);
const serverResult = await runTestVats(serverKernel, serverConfig);

// The server's ocap URL is its bootstrap result
const serverURL = serverResult as string;

expect(typeof serverURL).toBe('string');
expect(serverURL).toMatch(/^ocap:/u);

// Configure the client with the server's URL
const setupResult = await clientKernel.queueMessage(
clientRootRef,
'setMaas',
[serverURL],
);
let response = kunser(setupResult);
expect(response).toBeDefined();
expect(response).toContain('MaaS service URL set');

// Tell the client to talk to the server
let expectedCount = 1;
const stepResult = await clientKernel.queueMessage(
clientRootRef,
'step',
[],
);
response = kunser(stepResult);
expect(response).toBeDefined();
expect(response).toContain(`next step: ${expectedCount} `);

// Kill the server and restart it with a fresh db connection
await serverKernel.stop();
serverKernel = await makeTestKernel(
'kernel2b',
await makeSQLKernelDatabase({ dbFilename: dbFile2 }),
directNetwork,
false,
'kernel2-peer',
'02',
);

// Tell the client to talk to the server a second time
expectedCount += 1;
const stepResult2 = await clientKernel.queueMessage(
clientRootRef,
'step',
[],
);
response = kunser(stepResult2);
expect(response).toBeDefined();
expect(response).toContain(`next step: ${expectedCount} `);

// Kill the client and restart it with a fresh db connection
await clientKernel.stop();
clientKernel = await makeTestKernel(
'kernel1b',
await makeSQLKernelDatabase({ dbFilename: dbFile1 }),
directNetwork,
false,
'kernel1-peer',
'01',
);

// Tell the client to talk to the server a third time
expectedCount += 1;
const stepResult3 = await clientKernel.queueMessage(
clientRootRef,
'step',
[],
);
response = kunser(stepResult3);
expect(response).toBeDefined();
expect(response).toContain(`next step: ${expectedCount} `);

// Stop the local kernels before the temp dir is cleaned up
await Promise.all([clientKernel.stop(), serverKernel.stop()]);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
});

Expand Down
Loading
Loading