86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
vi.mock('../../src/env', () => ({ env: { boxIdleMs: 1000 } }));
|
|
vi.mock('../../src/runtime/docker', () => ({
|
|
ensureUserContainer: vi.fn((args: { username: string }) =>
|
|
Promise.resolve(`spoon-box-${args.username}`),
|
|
),
|
|
stopWorkspaceContainer: vi.fn(() => Promise.resolve()),
|
|
userContainerName: (username: string) => `spoon-box-${username}`,
|
|
listWorkspaceContainerNames: vi.fn(() => Promise.resolve([])),
|
|
}));
|
|
|
|
const load = async () => await import('../../src/user-container');
|
|
|
|
describe('box registry', () => {
|
|
beforeEach(() => vi.useFakeTimers());
|
|
afterEach(async () => {
|
|
const module = await load();
|
|
module._resetBoxRegistryForTests();
|
|
vi.useRealTimers();
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
test('serializes concurrent acquire into a single docker run', async () => {
|
|
const module = await load();
|
|
const docker = await import('../../src/runtime/docker');
|
|
const [a, b] = await Promise.all([
|
|
module.acquireUserBox({
|
|
username: 'alice',
|
|
workdir: '/w',
|
|
containerHome: '/home/alice',
|
|
}),
|
|
module.acquireUserBox({
|
|
username: 'alice',
|
|
workdir: '/w',
|
|
containerHome: '/home/alice',
|
|
}),
|
|
]);
|
|
expect(docker.ensureUserContainer).toHaveBeenCalledTimes(1);
|
|
expect(a.boxName).toBe('spoon-box-alice');
|
|
a.release();
|
|
vi.advanceTimersByTime(2000);
|
|
expect(docker.stopWorkspaceContainer).not.toHaveBeenCalled();
|
|
b.release();
|
|
vi.advanceTimersByTime(2000);
|
|
expect(docker.stopWorkspaceContainer).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('double-release of one handle does not reap a box held elsewhere', async () => {
|
|
const module = await load();
|
|
const docker = await import('../../src/runtime/docker');
|
|
const a = await module.acquireUserBox({
|
|
username: 'bob',
|
|
workdir: '/w',
|
|
containerHome: '/home/bob',
|
|
});
|
|
const b = await module.acquireUserBox({
|
|
username: 'bob',
|
|
workdir: '/w',
|
|
containerHome: '/home/bob',
|
|
});
|
|
a.release();
|
|
a.release();
|
|
vi.advanceTimersByTime(2000);
|
|
expect(docker.stopWorkspaceContainer).not.toHaveBeenCalled();
|
|
b.release();
|
|
vi.advanceTimersByTime(2000);
|
|
expect(docker.stopWorkspaceContainer).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('reconcileExistingBoxes adopts orphans with zero refs and reaps them', async () => {
|
|
const module = await load();
|
|
const docker = await import('../../src/runtime/docker');
|
|
(
|
|
docker.listWorkspaceContainerNames as unknown as ReturnType<typeof vi.fn>
|
|
).mockResolvedValueOnce(['spoon-box-carol']);
|
|
await module.reconcileExistingBoxes();
|
|
expect(module.runningBoxUsernames().has('carol')).toBe(true);
|
|
vi.advanceTimersByTime(2000);
|
|
expect(docker.stopWorkspaceContainer).toHaveBeenCalledWith(
|
|
'spoon-box-carol',
|
|
);
|
|
});
|
|
});
|