Files
smartvm/test/test.ts
T

721 lines
26 KiB
TypeScript
Raw Normal View History

import * as fs from 'fs';
import * as crypto from 'crypto';
import * as os from 'os';
import * as path from 'path';
2026-02-08 21:47:33 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
import {
BaseImageManager,
2026-02-08 21:47:33 +00:00
VMConfig,
SocketClient,
NetworkManager,
MicroVM,
2026-02-08 21:47:33 +00:00
SmartVM,
SmartVMError,
2026-02-08 21:47:33 +00:00
} from '../ts/index.js';
import type { IBaseImageBundle, IBaseImageHostedManifest, IMicroVMConfig } from '../ts/index.js';
async function getRejectedError(promise: Promise<unknown>): Promise<unknown> {
try {
await promise;
} catch (err) {
return err;
}
return undefined;
}
function sha256Buffer(buffer: Buffer): string {
return crypto.createHash('sha256').update(buffer).digest('hex');
}
2026-02-08 21:47:33 +00:00
// ============================================================
// VMConfig Tests
// ============================================================
const sampleConfig: IMicroVMConfig = {
id: 'test-vm-1',
bootSource: {
kernelImagePath: '/path/to/vmlinux',
bootArgs: 'console=ttyS0 reboot=k panic=1',
},
machineConfig: {
vcpuCount: 2,
memSizeMib: 256,
smt: false,
},
drives: [
{
driveId: 'rootfs',
pathOnHost: '/path/to/rootfs.ext4',
isRootDevice: true,
isReadOnly: false,
cacheType: 'Unsafe',
},
],
networkInterfaces: [
{
ifaceId: 'eth0',
hostDevName: 'tap0',
guestMac: '02:00:00:00:00:01',
},
],
};
tap.test('VMConfig - validate() should pass for valid config', async () => {
const vmConfig = new VMConfig(sampleConfig);
const result = vmConfig.validate();
expect(result.valid).toBeTrue();
expect(result.errors).toHaveLength(0);
});
tap.test('VMConfig - validate() should fail for missing bootSource', async () => {
const vmConfig = new VMConfig({
machineConfig: { vcpuCount: 1, memSizeMib: 128 },
} as any);
const result = vmConfig.validate();
expect(result.valid).toBeFalse();
expect(result.errors.length).toBeGreaterThan(0);
});
tap.test('VMConfig - validate() should fail for invalid vcpuCount', async () => {
const vmConfig = new VMConfig({
bootSource: { kernelImagePath: '/vmlinux' },
machineConfig: { vcpuCount: 0, memSizeMib: 128 },
});
const result = vmConfig.validate();
expect(result.valid).toBeFalse();
});
tap.test('VMConfig - validate() should fail for vcpuCount > 32', async () => {
const vmConfig = new VMConfig({
bootSource: { kernelImagePath: '/vmlinux' },
machineConfig: { vcpuCount: 64, memSizeMib: 128 },
});
const result = vmConfig.validate();
expect(result.valid).toBeFalse();
});
tap.test('VMConfig - validate() should fail for multiple root drives', async () => {
const vmConfig = new VMConfig({
bootSource: { kernelImagePath: '/vmlinux' },
machineConfig: { vcpuCount: 1, memSizeMib: 128 },
drives: [
{ driveId: 'rootfs1', pathOnHost: '/rootfs1', isRootDevice: true },
{ driveId: 'rootfs2', pathOnHost: '/rootfs2', isRootDevice: true },
],
});
const result = vmConfig.validate();
expect(result.valid).toBeFalse();
});
tap.test('VMConfig - validate() should fail for invalid vsock guestCid', async () => {
const vmConfig = new VMConfig({
bootSource: { kernelImagePath: '/vmlinux' },
machineConfig: { vcpuCount: 1, memSizeMib: 128 },
vsock: { guestCid: 2, udsPath: '/tmp/vsock.sock' },
});
const result = vmConfig.validate();
expect(result.valid).toBeFalse();
expect(result.errors).toContain('vsock.guestCid must be >= 3');
});
tap.test('VMConfig - constructor should not retain caller references', async () => {
const config: IMicroVMConfig = {
...sampleConfig,
networkInterfaces: [{ ifaceId: 'eth0', guestMac: '02:00:00:00:00:01' }],
mmds: { version: 'V2', networkInterfaces: ['eth0'] },
};
const vmConfig = new VMConfig(config);
config.networkInterfaces![0].guestMac = '02:00:00:00:00:02';
config.mmds!.networkInterfaces.push('eth1');
expect(vmConfig.toNetworkInterfacePayload(vmConfig.config.networkInterfaces![0]).guest_mac)
.toEqual('02:00:00:00:00:01');
expect(vmConfig.toMmdsConfigPayload()!.network_interfaces).toEqual(['eth0']);
});
2026-02-08 21:47:33 +00:00
tap.test('VMConfig - toBootSourcePayload() should generate correct snake_case', async () => {
const vmConfig = new VMConfig(sampleConfig);
const payload = vmConfig.toBootSourcePayload();
expect(payload.kernel_image_path).toEqual('/path/to/vmlinux');
expect(payload.boot_args).toEqual('console=ttyS0 reboot=k panic=1');
expect(payload.initrd_path).toBeUndefined();
});
tap.test('VMConfig - toMachineConfigPayload() should generate correct payload', async () => {
const vmConfig = new VMConfig(sampleConfig);
const payload = vmConfig.toMachineConfigPayload();
expect(payload.vcpu_count).toEqual(2);
expect(payload.mem_size_mib).toEqual(256);
expect(payload.smt).toEqual(false);
});
tap.test('VMConfig - toDrivePayload() should generate correct payload', async () => {
const vmConfig = new VMConfig(sampleConfig);
const payload = vmConfig.toDrivePayload(sampleConfig.drives![0]);
expect(payload.drive_id).toEqual('rootfs');
expect(payload.path_on_host).toEqual('/path/to/rootfs.ext4');
expect(payload.is_root_device).toEqual(true);
expect(payload.is_read_only).toEqual(false);
expect(payload.cache_type).toEqual('Unsafe');
});
tap.test('VMConfig - toNetworkInterfacePayload() should generate correct payload', async () => {
const vmConfig = new VMConfig(sampleConfig);
const payload = vmConfig.toNetworkInterfacePayload(sampleConfig.networkInterfaces![0]);
expect(payload.iface_id).toEqual('eth0');
expect(payload.host_dev_name).toEqual('tap0');
expect(payload.guest_mac).toEqual('02:00:00:00:00:01');
});
tap.test('VMConfig - toVsockPayload() should return null when no vsock configured', async () => {
const vmConfig = new VMConfig(sampleConfig);
expect(vmConfig.toVsockPayload()).toBeNull();
});
tap.test('VMConfig - toBalloonPayload() should return null when no balloon configured', async () => {
const vmConfig = new VMConfig(sampleConfig);
expect(vmConfig.toBalloonPayload()).toBeNull();
});
tap.test('VMConfig - toBalloonPayload() should generate correct payload', async () => {
const config: IMicroVMConfig = {
...sampleConfig,
balloon: { amountMib: 64, deflateOnOom: true, statsPollingIntervalS: 5 },
};
const vmConfig = new VMConfig(config);
const payload = vmConfig.toBalloonPayload();
expect(payload).not.toBeNull();
expect(payload!.amount_mib).toEqual(64);
expect(payload!.deflate_on_oom).toEqual(true);
expect(payload!.stats_polling_interval_s).toEqual(5);
});
tap.test('VMConfig - toVsockPayload() should generate correct payload', async () => {
const config: IMicroVMConfig = {
...sampleConfig,
vsock: { guestCid: 3, udsPath: '/tmp/vsock.sock' },
};
const vmConfig = new VMConfig(config);
const payload = vmConfig.toVsockPayload();
expect(payload).not.toBeNull();
expect(payload!.guest_cid).toEqual(3);
expect(payload!.uds_path).toEqual('/tmp/vsock.sock');
});
tap.test('VMConfig - toMmdsConfigPayload() should generate correct payload', async () => {
const config: IMicroVMConfig = {
...sampleConfig,
mmds: { version: 'V2', networkInterfaces: ['eth0'] },
};
const vmConfig = new VMConfig(config);
const payload = vmConfig.toMmdsConfigPayload();
expect(payload).not.toBeNull();
expect(payload!.version).toEqual('V2');
expect(payload!.network_interfaces).toEqual(['eth0']);
});
tap.test('VMConfig - toMetricsPayload() should generate correct payload', async () => {
const config: IMicroVMConfig = {
...sampleConfig,
metrics: { metricsPath: '/tmp/firecracker-metrics.fifo' },
};
const vmConfig = new VMConfig(config);
const payload = vmConfig.toMetricsPayload();
expect(payload).not.toBeNull();
expect(payload!.metrics_path).toEqual('/tmp/firecracker-metrics.fifo');
});
tap.test('VMConfig - toDrivePayload() should include rate limiter payloads', async () => {
const vmConfig = new VMConfig(sampleConfig);
const payload = vmConfig.toDrivePayload({
driveId: 'data',
pathOnHost: '/path/to/data.ext4',
isRootDevice: false,
rateLimiter: {
bandwidth: { size: 1000, refillTime: 2000, oneTimeBurst: 3000 },
ops: { size: 10, refillTime: 20, oneTimeBurst: 30 },
},
});
expect(payload.rate_limiter.bandwidth).toEqual({
size: 1000,
refill_time: 2000,
one_time_burst: 3000,
});
expect(payload.rate_limiter.ops).toEqual({
size: 10,
refill_time: 20,
one_time_burst: 30,
});
});
2026-02-08 21:47:33 +00:00
tap.test('VMConfig - toLoggerPayload() should generate correct payload', async () => {
const config: IMicroVMConfig = {
...sampleConfig,
logger: { logPath: '/tmp/fc.log', level: 'Debug', showLogOrigin: true },
};
const vmConfig = new VMConfig(config);
const payload = vmConfig.toLoggerPayload();
expect(payload).not.toBeNull();
expect(payload!.log_path).toEqual('/tmp/fc.log');
expect(payload!.level).toEqual('Debug');
expect(payload!.show_log_origin).toEqual(true);
});
// ============================================================
// SocketClient Tests
// ============================================================
tap.test('SocketClient - URL construction', async () => {
const client = new SocketClient({ socketPath: '/tmp/test.sock' });
// We can't directly access buildUrl, but we can verify the client instantiates
expect(client).toBeTruthy();
});
// ============================================================
// BaseImageManager Tests
// ============================================================
tap.test('BaseImageManager - instantiation with defaults', async () => {
const manager = new BaseImageManager();
expect(manager.getCacheDir()).toEqual(path.join(os.tmpdir(), '.smartvm', 'base-images'));
expect(manager.getMaxStoredBaseImages()).toEqual(2);
});
tap.test('BaseImageManager - rejects invalid maxStoredBaseImages', async () => {
let error: unknown;
try {
new BaseImageManager({ maxStoredBaseImages: 0 });
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_CACHE_LIMIT');
});
tap.test('BaseImageManager - pruneBaseImageCache() should evict old bundles', async () => {
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-base-image-test-'));
const manager = new BaseImageManager({ cacheDir, maxStoredBaseImages: 2 });
const originalWarn = console.warn;
const warnings: string[] = [];
console.warn = (message?: any) => {
warnings.push(String(message));
};
const createManifest = async (bundleId: string, lastAccessedAt: string) => {
const bundleDir = path.join(cacheDir, bundleId);
await fs.promises.mkdir(bundleDir, { recursive: true });
const bundle: IBaseImageBundle = {
preset: 'lts',
arch: 'x86_64',
ciVersion: 'v1.7',
firecrackerVersion: 'v1.7.0',
bundleId,
bundleDir,
kernelImagePath: path.join(bundleDir, 'vmlinux'),
rootfsPath: path.join(bundleDir, 'rootfs.ext4'),
rootfsType: 'ext4',
rootfsIsReadOnly: false,
bootArgs: 'console=ttyS0 reboot=k panic=1 pci=off',
source: {
bucketUrl: 'https://s3.amazonaws.com/spec.ccfc.min',
kernelKey: 'kernel',
rootfsKey: 'rootfs',
},
createdAt: lastAccessedAt,
lastAccessedAt,
};
await fs.promises.writeFile(path.join(bundleDir, 'manifest.json'), `${JSON.stringify(bundle, null, 2)}\n`);
};
try {
await createManifest('old', '2024-01-01T00:00:00.000Z');
await createManifest('middle', '2024-01-02T00:00:00.000Z');
await createManifest('new', '2024-01-03T00:00:00.000Z');
const evicted = await manager.pruneBaseImageCache('new');
expect(evicted).toEqual(['old']);
expect(warnings.length).toEqual(1);
expect(warnings[0]).toInclude('Evicting old');
expect(fs.existsSync(path.join(cacheDir, 'old'))).toBeFalse();
expect(fs.existsSync(path.join(cacheDir, 'middle'))).toBeTrue();
expect(fs.existsSync(path.join(cacheDir, 'new'))).toBeTrue();
} finally {
console.warn = originalWarn;
await fs.promises.rm(cacheDir, { recursive: true, force: true });
}
});
tap.test('BaseImageManager - ensureBaseImage() should copy hosted manifest artifacts', async () => {
const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-test-'));
const cacheDir = path.join(workDir, 'cache');
const assetsDir = path.join(workDir, 'assets');
await fs.promises.mkdir(assetsDir, { recursive: true });
const kernelBuffer = Buffer.from('fake-kernel');
const rootfsBuffer = Buffer.from('fake-rootfs');
const kernelPath = path.join(assetsDir, 'vmlinux-test');
const rootfsPath = path.join(assetsDir, 'rootfs-test.ext4');
await fs.promises.writeFile(kernelPath, kernelBuffer);
await fs.promises.writeFile(rootfsPath, rootfsBuffer);
const manifest: IBaseImageHostedManifest = {
schemaVersion: 1,
bundleId: 'smartvm-minimal-test',
name: 'SmartVM minimal test bundle',
arch: 'x86_64',
firecrackerVersion: 'v1.15.1',
rootfsType: 'ext4',
rootfsIsReadOnly: false,
bootArgs: 'console=ttyS0 reboot=k panic=1 pci=off',
kernel: {
path: kernelPath,
fileName: 'vmlinux',
sha256: sha256Buffer(kernelBuffer),
sizeBytes: kernelBuffer.length,
},
rootfs: {
path: rootfsPath,
fileName: 'rootfs.ext4',
sha256: sha256Buffer(rootfsBuffer),
sizeBytes: rootfsBuffer.length,
},
};
const manifestPath = path.join(workDir, 'manifest.json');
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
try {
const manager = new BaseImageManager({ cacheDir });
const bundle = await manager.ensureBaseImage({ manifestPath });
expect(bundle.preset).toEqual('hosted');
expect(bundle.bundleId).toEqual('smartvm-minimal-test');
expect(bundle.firecrackerVersion).toEqual('v1.15.1');
expect(bundle.source.type).toEqual('hosted-manifest');
expect(bundle.source.manifestPath).toEqual(manifestPath);
expect(fs.existsSync(bundle.kernelImagePath)).toBeTrue();
expect(fs.existsSync(bundle.rootfsPath)).toBeTrue();
expect(bundle.checksums!.kernelSha256).toEqual(sha256Buffer(kernelBuffer));
expect(bundle.checksums!.rootfsSha256).toEqual(sha256Buffer(rootfsBuffer));
} finally {
await fs.promises.rm(workDir, { recursive: true, force: true });
}
});
tap.test('BaseImageManager - ensureBaseImage() should redownload corrupted cached artifacts', async () => {
const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-cache-test-'));
const cacheDir = path.join(workDir, 'cache');
const assetsDir = path.join(workDir, 'assets');
await fs.promises.mkdir(assetsDir, { recursive: true });
const kernelBuffer = Buffer.from('fresh-kernel');
const rootfsBuffer = Buffer.from('fresh-rootfs');
const kernelPath = path.join(assetsDir, 'vmlinux-test');
const rootfsPath = path.join(assetsDir, 'rootfs-test.ext4');
await fs.promises.writeFile(kernelPath, kernelBuffer);
await fs.promises.writeFile(rootfsPath, rootfsBuffer);
const manifest: IBaseImageHostedManifest = {
schemaVersion: 1,
bundleId: 'smartvm-corruption-test',
arch: 'x86_64',
firecrackerVersion: 'v1.15.1',
rootfsType: 'ext4',
kernel: {
path: kernelPath,
fileName: 'vmlinux',
sha256: sha256Buffer(kernelBuffer),
sizeBytes: kernelBuffer.length,
},
rootfs: {
path: rootfsPath,
fileName: 'rootfs.ext4',
sha256: sha256Buffer(rootfsBuffer),
sizeBytes: rootfsBuffer.length,
},
};
const manifestPath = path.join(workDir, 'manifest.json');
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
try {
const manager = new BaseImageManager({ cacheDir });
const firstBundle = await manager.ensureBaseImage({ manifestPath });
await fs.promises.writeFile(firstBundle.kernelImagePath, 'tampered-kernel');
const secondBundle = await manager.ensureBaseImage({ manifestPath });
expect(await fs.promises.readFile(secondBundle.kernelImagePath, 'utf8')).toEqual('fresh-kernel');
expect(secondBundle.checksums!.kernelSha256).toEqual(sha256Buffer(kernelBuffer));
} finally {
await fs.promises.rm(workDir, { recursive: true, force: true });
}
});
tap.test('BaseImageManager - ensureBaseImage() should reject hosted manifest arch mismatch', async () => {
const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-invalid-test-'));
const manifestPath = path.join(workDir, 'manifest.json');
const manifest: IBaseImageHostedManifest = {
schemaVersion: 1,
bundleId: 'smartvm-invalid-arch-test',
arch: 'aarch64',
firecrackerVersion: 'v1.15.1',
rootfsType: 'ext4',
kernel: { path: path.join(workDir, 'vmlinux') },
rootfs: { path: path.join(workDir, 'rootfs.ext4') },
};
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
try {
const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache'), arch: 'x86_64' });
const error = await getRejectedError(manager.ensureBaseImage({ manifestPath }));
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_MANIFEST');
} finally {
await fs.promises.rm(workDir, { recursive: true, force: true });
}
});
tap.test('BaseImageManager - ensureBaseImage() should reject hosted manifest fileName traversal', async () => {
const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-filename-test-'));
const manifestPath = path.join(workDir, 'manifest.json');
const manifest: IBaseImageHostedManifest = {
schemaVersion: 1,
bundleId: 'smartvm-invalid-filename-test',
arch: 'x86_64',
firecrackerVersion: 'v1.15.1',
rootfsType: 'ext4',
kernel: { path: path.join(workDir, 'vmlinux'), fileName: '../vmlinux' },
rootfs: { path: path.join(workDir, 'rootfs.ext4'), fileName: 'rootfs.ext4' },
};
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
try {
const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache') });
const error = await getRejectedError(manager.ensureBaseImage({ manifestPath }));
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_MANIFEST');
} finally {
await fs.promises.rm(workDir, { recursive: true, force: true });
}
});
tap.test('BaseImageManager - ensureBaseImage() should reject hosted URL artifacts without sha256', async () => {
const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-url-test-'));
const manifestPath = path.join(workDir, 'manifest.json');
const manifest: IBaseImageHostedManifest = {
schemaVersion: 1,
bundleId: 'smartvm-invalid-url-test',
arch: 'x86_64',
firecrackerVersion: 'v1.15.1',
rootfsType: 'ext4',
kernel: { url: 'https://example.com/vmlinux' },
rootfs: { path: path.join(workDir, 'rootfs.ext4') },
};
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
try {
const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache') });
const error = await getRejectedError(manager.ensureBaseImage({ manifestPath }));
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_MANIFEST');
} finally {
await fs.promises.rm(workDir, { recursive: true, force: true });
}
});
tap.test('BaseImageManager - hosted preset should require a manifest', async () => {
const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-missing-test-'));
try {
const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache') });
const error = await getRejectedError(manager.ensureBaseImage({ preset: 'hosted' }));
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('BASE_IMAGE_MANIFEST_FAILED');
} finally {
await fs.promises.rm(workDir, { recursive: true, force: true });
}
});
2026-02-08 21:47:33 +00:00
// ============================================================
// NetworkManager Tests
// ============================================================
tap.test('NetworkManager - allocateIp() should allocate sequential IPs', async () => {
const nm = new NetworkManager({ subnet: '172.30.0.0/24' });
const ip1 = nm.allocateIp();
const ip2 = nm.allocateIp();
const ip3 = nm.allocateIp();
expect(ip1).toEqual('172.30.0.2');
expect(ip2).toEqual('172.30.0.3');
expect(ip3).toEqual('172.30.0.4');
});
tap.test('NetworkManager - allocateIp() should normalize non-network CIDR input', async () => {
const nm = new NetworkManager({ subnet: '10.20.30.17/29' });
expect(nm.allocateIp()).toEqual('10.20.30.18');
expect(nm.allocateIp()).toEqual('10.20.30.19');
});
tap.test('NetworkManager - allocateIp() should fail when subnet is exhausted', async () => {
const nm = new NetworkManager({ subnet: '192.168.100.0/30' });
expect(nm.allocateIp()).toEqual('192.168.100.2');
let error: unknown;
try {
nm.allocateIp();
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('IP_EXHAUSTED');
});
tap.test('NetworkManager - constructor should reject invalid subnet input', async () => {
let error: unknown;
try {
new NetworkManager({ subnet: '10.0.0.0/31' });
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_SUBNET');
});
tap.test('NetworkManager - constructor should reject malformed IPv4 octets', async () => {
for (const subnet of ['10..0.1/24', '10.0x10.0.1/24', '10.0.0.1 /24']) {
let error: unknown;
try {
new NetworkManager({ subnet });
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_SUBNET');
}
});
tap.test('NetworkManager - constructor should reject invalid bridge names', async () => {
let error: unknown;
try {
new NetworkManager({ bridgeName: 'bad bridge' });
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_INTERFACE_NAME');
});
2026-02-08 21:47:33 +00:00
tap.test('NetworkManager - generateMac() should generate locally-administered MACs', async () => {
const nm = new NetworkManager();
const mac1 = nm.generateMac('vm1', 'eth0');
const mac2 = nm.generateMac('vm2', 'eth0');
const mac3 = nm.generateMac('vm1', 'eth0');
// Should start with 02: (locally administered)
expect(mac1.startsWith('02:')).toBeTrue();
expect(mac2.startsWith('02:')).toBeTrue();
// Same inputs should produce same MAC (deterministic)
expect(mac1).toEqual(mac3);
// Different inputs should produce different MACs
expect(mac1).not.toEqual(mac2);
// Should be valid MAC format (6 hex pairs separated by colons)
expect(mac1).toMatch(/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/);
});
tap.test('NetworkManager - generateTapName() should respect IFNAMSIZ limit', async () => {
const nm = new NetworkManager();
const name1 = nm.generateTapName('abcdefgh-1234-5678', 'eth0');
const name2 = nm.generateTapName('short', 'eth0');
// Should not exceed 15 characters
expect(name1.length).toBeLessThanOrEqual(15);
expect(name2.length).toBeLessThanOrEqual(15);
// Should start with 'sv' prefix
expect(name1.startsWith('sv')).toBeTrue();
expect(name2.startsWith('sv')).toBeTrue();
});
tap.test('NetworkManager - getGuestNetworkBootArgs() should format correctly', async () => {
const nm = new NetworkManager();
const tap = {
tapName: 'svtest0eth0',
guestIp: '172.30.0.2',
gatewayIp: '172.30.0.1',
subnetMask: '255.255.255.0',
mac: '02:00:00:00:00:01',
};
const bootArgs = nm.getGuestNetworkBootArgs(tap);
expect(bootArgs).toEqual('ip=172.30.0.2::172.30.0.1:255.255.255.0::eth0:off');
});
// ============================================================
// MicroVM Tests
// ============================================================
tap.test('MicroVM - invalid lifecycle calls should throw SmartVMError', async () => {
const vm = new MicroVM(
'lifecycle-vm',
sampleConfig,
'/bin/false',
'/tmp/smartvm-lifecycle.sock',
new NetworkManager(),
);
const pauseError = await getRejectedError(vm.pause());
expect(pauseError).toBeInstanceOf(SmartVMError);
expect((pauseError as SmartVMError).code).toEqual('INVALID_STATE');
const infoError = await getRejectedError(vm.getInfo());
expect(infoError).toBeInstanceOf(SmartVMError);
expect((infoError as SmartVMError).code).toEqual('NO_CLIENT');
});
2026-02-08 21:47:33 +00:00
// ============================================================
// SmartVM Tests
// ============================================================
tap.test('SmartVM - instantiation with defaults', async () => {
const smartvm = new SmartVM();
expect(smartvm).toBeTruthy();
expect(smartvm.imageManager).toBeTruthy();
expect(smartvm.baseImageManager).toBeTruthy();
2026-02-08 21:47:33 +00:00
expect(smartvm.networkManager).toBeTruthy();
expect(smartvm.vmCount).toEqual(0);
expect(smartvm.listVMs()).toHaveLength(0);
});
tap.test('SmartVM - instantiation with custom options', async () => {
const smartvm = new SmartVM({
dataDir: '/tmp/smartvm-test',
arch: 'aarch64',
bridgeName: 'testbr0',
subnet: '10.0.0.0/24',
});
expect(smartvm).toBeTruthy();
});
tap.test('SmartVM - createVM() should track created VMs', async () => {
const smartvm = new SmartVM({
dataDir: '/tmp/smartvm-test',
firecrackerBinaryPath: '/bin/false',
});
const vm = await smartvm.createVM(sampleConfig);
expect(smartvm.vmCount).toEqual(1);
expect(smartvm.getVM(vm.id)).toEqual(vm);
expect(smartvm.listVMs()).toEqual([vm.id]);
await smartvm.cleanup();
expect(smartvm.vmCount).toEqual(0);
});
2026-02-08 21:47:33 +00:00
export default tap.start();