node-os-utils
Version:
Advanced cross-platform operating system monitoring utilities with TypeScript support
237 lines • 12.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const os_1 = __importDefault(require("os"));
const macos_adapter_1 = require("../../../src/adapters/macos-adapter");
const errors_1 = require("../../../src/types/errors");
describe('MacOSAdapter 内部解析逻辑', () => {
it('应当将 RSS 转换为字节并保留内存百分比', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const data = {
summary: '123 1 20480 12.5 3.4 R user',
command: '/usr/bin/example --flag',
start: 'Mon Mar 11 10:00:00 2024'
};
const result = internal.parseProcessInfo(data, 123);
(0, chai_1.expect)(result.pid).to.equal(123);
(0, chai_1.expect)(result.ppid).to.equal(1);
(0, chai_1.expect)(result.command).to.equal('/usr/bin/example --flag');
(0, chai_1.expect)(result.memoryUsage).to.equal(20480 * 1024);
(0, chai_1.expect)(result.memoryPercentage).to.be.closeTo(3.4, 0.0001);
(0, chai_1.expect)(result.cpuUsage).to.be.closeTo(12.5, 0.0001);
(0, chai_1.expect)(result.state).to.equal('R');
});
it('应当根据 vm_stat 中的页面大小正确计算内存', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const vmStatOutput = [
'Mach Virtual Memory Statistics: (page size of 16384 bytes)',
'Pages free: 100.',
'Pages active: 200.',
'Pages inactive: 300.',
'Pages wired down: 400.',
'Pages occupied by compressor: 50.',
''
].join('\n');
const totalMem = (16 * 1024 * 1024 * 1024).toString();
const pressureOutput = 'System-wide memory pressure: warn';
const result = internal.parseMemoryInfo(vmStatOutput, totalMem, pressureOutput);
const pageSize = 16384;
(0, chai_1.expect)(result.total).to.equal(16 * 1024 * 1024 * 1024);
(0, chai_1.expect)(result.free).to.equal(100 * pageSize);
(0, chai_1.expect)(result.active).to.equal(200 * pageSize);
(0, chai_1.expect)(result.inactive).to.equal(300 * pageSize);
(0, chai_1.expect)(result.wired).to.equal(400 * pageSize);
(0, chai_1.expect)(result.compressed).to.equal(50 * pageSize);
(0, chai_1.expect)(result.used).to.equal((200 + 400 + 50) * pageSize);
(0, chai_1.expect)(result.available).to.equal(result.total - result.used);
(0, chai_1.expect)(result.pressure.level).to.equal('high');
});
it('应当正确解析 iostat 输出的磁盘数据', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const iostatOutput = `
disk0 disk1
KB/t tps MB/s KB/t tps MB/s
16.00 1 0.01 32.00 2 0.06
`;
const ioResult = internal.parseDiskIO(iostatOutput);
const statsResult = internal.parseDiskStats(iostatOutput);
(0, chai_1.expect)(ioResult).to.have.lengthOf(2);
(0, chai_1.expect)(ioResult[0].device).to.equal('disk0');
(0, chai_1.expect)(ioResult[0].kbPerTransfer).to.be.closeTo(16, 0.001);
(0, chai_1.expect)(ioResult[0].mbPerSec).to.be.closeTo(0.01, 0.0001);
(0, chai_1.expect)(ioResult[0].readSpeed).to.be.closeTo(0.01 * 1024 * 1024, 1e-6);
(0, chai_1.expect)(statsResult[0].device).to.equal('disk0');
(0, chai_1.expect)(statsResult[0].readCount).to.equal(1);
(0, chai_1.expect)(statsResult[0].readSpeed).to.be.closeTo(0.01 * 1024 * 1024, 1e-6);
(0, chai_1.expect)(statsResult[0].writeBytes).to.equal(0);
});
it('应当在解析进程环境变量时保留带空格的值', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const envOutput = [
'123 ?? Ss 0:00.01 /usr/bin/node --inspect',
'PATH=/usr/bin:/bin HOME=/Users/test PID=1234 APP_NAME=My App With Spaces LANG=en_US.UTF-8',
'LOG_LEVEL="debug mode" EMPTY_VAR= NEXT=final'
].join('\n');
const result = internal.parseEnvironment(envOutput);
(0, chai_1.expect)(result.PATH).to.equal('/usr/bin:/bin');
(0, chai_1.expect)(result.HOME).to.equal('/Users/test');
(0, chai_1.expect)(result).to.not.have.property('PID');
(0, chai_1.expect)(result.APP_NAME).to.equal('My App With Spaces');
(0, chai_1.expect)(result.LANG).to.equal('en_US.UTF-8');
(0, chai_1.expect)(result.LOG_LEVEL).to.equal('"debug mode"');
(0, chai_1.expect)(result.EMPTY_VAR).to.equal('');
(0, chai_1.expect)(result.NEXT).to.equal('final');
});
it('应当从 ifconfig 输出中提取 MAC 地址', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const ifconfigOutput = [
'lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384',
'\tinet 127.0.0.1 netmask 0xff000000',
'en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500',
'\tether f0:18:98:73:ad:10',
'\tinet 192.168.1.2 netmask 0xffffff00 broadcast 192.168.1.255',
'\tinet6 fe80::c86:abcd:ef12%en0 prefixlen 64'
].join('\n');
const result = internal.parseNetworkInterfaces(ifconfigOutput);
const en0 = result.find((iface) => iface.name === 'en0');
(0, chai_1.expect)(en0).to.exist;
(0, chai_1.expect)(en0.mac).to.equal('f0:18:98:73:ad:10');
(0, chai_1.expect)(en0.addresses).to.have.lengthOf(2);
});
it('应当解析系统信息并计算启动时间', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const originalUptime = os_1.default.uptime;
os_1.default.uptime = () => 1234;
try {
const uname = 'Darwin MacBook-Pro 23.4.0 Darwin Kernel Version';
const uptime = ' 10:22 up 3:24, 2 users, load averages: 1.23 0.56 0.42';
const loadavg = '{ 1.23 0.56 0.42 }';
const swVers = 'ProductName: macOS\nProductVersion: 14.4\nBuildVersion: 23E214';
const result = internal.parseSystemInfo(uname, uptime, loadavg, swVers);
(0, chai_1.expect)(result.platform).to.equal('darwin');
(0, chai_1.expect)(result.version).to.equal(swVers.trim());
(0, chai_1.expect)(result.loadAverage.load1).to.be.closeTo(1.23, 0.0001);
(0, chai_1.expect)(result.uptimeSeconds).to.equal(1234);
(0, chai_1.expect)(result.bootTime).to.be.closeTo(Date.now() - 1234 * 1000, 50);
}
finally {
os_1.default.uptime = originalUptime;
}
});
it('应当解析 netstat 输出中的收发字节统计', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const netstatOutput = [
'Name Mtu Net/Dest Address Ibytes Obytes Ipackets Ierrs Opackets Oerrs Collisions',
'en0 1500 link#5 f0:18:98:73:ad:10 42 1 4096 84 0 8192 2'
].join('\n');
const stats = internal.parseNetworkStats(netstatOutput);
(0, chai_1.expect)(stats).to.have.lengthOf(1);
(0, chai_1.expect)(stats[0].interface).to.equal('en0');
(0, chai_1.expect)(stats[0].rxPackets).to.equal(42);
(0, chai_1.expect)(stats[0].rxErrors).to.equal(1);
(0, chai_1.expect)(stats[0].rxBytes).to.equal(4096);
(0, chai_1.expect)(stats[0].txPackets).to.equal(84);
(0, chai_1.expect)(stats[0].txBytes).to.equal(8192);
(0, chai_1.expect)(stats[0].collisions).to.equal(2);
});
it('应当从 route 输出中解析默认网关', () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const routeOutput = [
' route to: default',
'destination: default',
' mask: default',
' gateway: 192.168.1.1',
' interface: en0',
' flags: <UP,GATEWAY,DONE,STATIC>'
].join('\n');
const result = internal.parseDefaultGateway(routeOutput);
(0, chai_1.expect)(result).to.deep.equal({ gateway: '192.168.1.1', interface: 'en0' });
});
it('T022: getCPUInfo() 在 sysctl 命令全部失败时应降级到 os.cpus() 数据而非抛出异常', async () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
// stub executeCommand 使所有 sysctl 命令失败
internal.executeCommand = async () => {
throw new errors_1.MonitorError('sysctl 不可用', errors_1.ErrorCode.COMMAND_FAILED, 'darwin');
};
const result = await adapter.getCPUInfo();
(0, chai_1.expect)(result).to.be.an('object');
(0, chai_1.expect)(result.cores).to.be.a('number').and.to.be.greaterThan(0);
(0, chai_1.expect)(result.threads).to.be.a('number').and.to.be.greaterThan(0);
(0, chai_1.expect)(result.model).to.be.a('string').and.to.have.length.greaterThan(0);
});
it('在 top 失败时应回退到 iostat 获取 CPU 使用率', async () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
const commands = [];
internal.executeCommand = async (command) => {
commands.push(command);
if (command.startsWith('top')) {
throw new Error('top failed');
}
if (command.startsWith('iostat')) {
return {
stdout: ' cpu\n us sy id\n 12 5 83\n',
stderr: '',
exitCode: 0,
platform: 'darwin',
executionTime: 1,
command
};
}
throw new Error(`unexpected command: ${command}`);
};
const usage = await adapter.getCPUUsage();
(0, chai_1.expect)(commands).to.deep.equal(['top -l 1 -n 0', 'iostat -c 1']);
(0, chai_1.expect)(usage.overall).to.be.closeTo(17, 0.0001);
(0, chai_1.expect)(usage.user).to.be.closeTo(12, 0.0001);
(0, chai_1.expect)(usage.system).to.be.closeTo(5, 0.0001);
(0, chai_1.expect)(usage.idle).to.be.closeTo(83, 0.0001);
});
it('powermetrics 无法执行时应返回不支持错误', async () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
internal.executeCommand = async () => {
throw Object.assign(new Error('permission denied'), { code: 'EACCES' });
};
try {
await adapter.getCPUTemperature();
chai_1.expect.fail('should not succeed');
}
catch (error) {
(0, chai_1.expect)(error).to.be.instanceOf(errors_1.MonitorError);
const monitorError = error;
(0, chai_1.expect)(monitorError.code).to.equal(errors_1.ErrorCode.PLATFORM_NOT_SUPPORTED);
(0, chai_1.expect)(monitorError.platform).to.equal('darwin');
}
});
it('读取文件权限不足时应抛出权限错误', async () => {
const adapter = new macos_adapter_1.MacOSAdapter();
const internal = adapter;
internal.executeCommand = async () => {
throw Object.assign(new Error('permission denied'), { code: 'EACCES' });
};
try {
await adapter.readFile('/private/secret');
chai_1.expect.fail('should not succeed');
}
catch (error) {
(0, chai_1.expect)(error).to.be.instanceOf(errors_1.MonitorError);
const monitorError = error;
(0, chai_1.expect)(monitorError.code).to.equal(errors_1.ErrorCode.PERMISSION_DENIED);
(0, chai_1.expect)(monitorError.details.path).to.equal('/private/secret');
}
});
});
//# sourceMappingURL=macos-adapter.test.js.map