Files
smartvm/test/test.ts

255 lines
8.6 KiB
TypeScript
Raw Permalink Normal View History

2026-02-08 21:47:33 +00:00
import { tap, expect } from '@git.zone/tstest/tapbundle';
import {
VMConfig,
SocketClient,
NetworkManager,
SmartVM,
} from '../ts/index.js';
import type { IMicroVMConfig } from '../ts/index.js';
// ============================================================
// 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 - 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 - 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();
});
// ============================================================
// 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 - 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');
});
// ============================================================
// SmartVM Tests
// ============================================================
tap.test('SmartVM - instantiation with defaults', async () => {
const smartvm = new SmartVM();
expect(smartvm).toBeTruthy();
expect(smartvm.imageManager).toBeTruthy();
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();
});
export default tap.start();