initial
This commit is contained in:
254
test/test.ts
Normal file
254
test/test.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user