basant307/AI_Governance_Project
045
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7import * as os from 'node:os';8import * as path from 'node:path';9import * as fs from 'node:fs';10 11import { afterEach, describe, expect, it, vi } from 'vitest';12import {13 clearHeapSnapshotRateLimit,14 collectMemoryPressureSamples,15 formatMemoryDiagnostics,16 formatMemoryPressureSamples,17 getMemoryDiagnostics,18 isHighHeapPressure,19 writeMemoryHeapSnapshot,20} from './memoryDiagnostics.js';21 22describe('memoryDiagnostics', () => {23 afterEach(() => {24 clearHeapSnapshotRateLimit();25 vi.restoreAllMocks();26 vi.doUnmock('node:fs');27 vi.doUnmock('node:v8');28 });29 30 it('collects baseline memory fields', () => {31 const diagnostics = getMemoryDiagnostics();32 33 expect(diagnostics.process.pid).toBe(process.pid);34 expect(diagnostics.process.nodeVersion).toBe(process.version);35 expect(diagnostics.process.platform).toBe(process.platform);36 expect(diagnostics.process.arch).toBe(process.arch);37 expect(diagnostics.memory.rss).toBeGreaterThan(0);38 expect(diagnostics.memory.heapTotal).toBeGreaterThan(0);39 expect(diagnostics.memory.heapUsed).toBeGreaterThan(0);40 expect(diagnostics.memory.external).toBeGreaterThanOrEqual(0);41 expect(diagnostics.memory.arrayBuffers).toBeGreaterThanOrEqual(0);42 expect(diagnostics.v8.heapStatistics).toBeDefined();43 expect(diagnostics.v8.heapSpaces.length).toBeGreaterThan(0);44 expect(diagnostics.activeHandles.count).toBeGreaterThanOrEqual(0);45 expect(diagnostics.activeRequests.count).toBeGreaterThanOrEqual(0);46 });47 48 it('formats a paste-safe human-readable report with key sections', () => {49 vi.useFakeTimers();50 vi.setSystemTime(new Date('2026-05-15T12:00:00.000Z'));51 52 try {53 const report = formatMemoryDiagnostics({54 generatedAt: new Date().toISOString(),55 process: {56 pid: 123,57 nodeVersion: 'v22.0.0',58 platform: 'linux',59 arch: 'x64',60 uptimeSeconds: 42.4,61 },62 memory: {63 rss: 100 * 1024 * 1024,64 heapTotal: 80 * 1024 * 1024,65 heapUsed: 40 * 1024 * 1024,66 external: 5 * 1024 * 1024,67 arrayBuffers: 2 * 1024 * 1024,68 },69 v8: {70 heapStatistics: {71 heap_size_limit: 4096 * 1024 * 1024,72 total_available_size: 3000 * 1024 * 1024,73 },74 heapSpaces: [75 {76 space_name: 'old_space',77 space_size: 30 * 1024 * 1024,78 space_used_size: 20 * 1024 * 1024,79 },80 ],81 },82 activeHandles: { count: 3, unavailable: false },83 activeRequests: { count: 1, unavailable: false },84 });85 86 expect(report).toContain('Memory diagnostics');87 expect(report).toContain('Generated: 2026-05-15T12:00:00.000Z');88 expect(report).toContain('Node.js: v22.0.0');89 expect(report).toContain('RSS: 100.0 MiB');90 expect(report).toContain('Heap used / total: 40.0 MiB / 80.0 MiB');91 expect(report).toContain('External: 5.0 MiB');92 expect(report).toContain('Array buffers: 2.0 MiB');93 expect(report).toContain('Heap size limit: 4096.0 MiB');94 expect(report).toContain('old_space: 20.0 MiB / 30.0 MiB');95 expect(report).toContain('Active handles: 3');96 expect(report).toContain('Active requests: 1');97 expect(report).toContain('Assessment');98 expect(report).toContain('Status: ok');99 expect(report).toContain('Heap pressure: 1.0%');100 } finally {101 vi.useRealTimers();102 }103 });104 105 it('surfaces high heap pressure with actionable recommendations', () => {106 const report = formatMemoryDiagnostics({107 generatedAt: '2026-05-15T12:00:00.000Z',108 process: {109 pid: 123,110 nodeVersion: 'v22.0.0',111 platform: 'linux',112 arch: 'x64',113 uptimeSeconds: 120,114 },115 memory: {116 rss: 3900 * 1024 * 1024,117 heapTotal: 3600 * 1024 * 1024,118 heapUsed: 3500 * 1024 * 1024,119 external: 20 * 1024 * 1024,120 arrayBuffers: 10 * 1024 * 1024,121 },122 v8: {123 heapStatistics: {124 heap_size_limit: 4096 * 1024 * 1024,125 total_available_size: 200 * 1024 * 1024,126 },127 heapSpaces: [],128 },129 activeHandles: { count: 3, unavailable: false },130 activeRequests: { count: 1, unavailable: false },131 });132 133 expect(report).toContain('Status: warn');134 expect(report).toContain('Heap pressure: 85.4%');135 expect(report).toContain('V8 heap usage is high');136 expect(report).toContain('restart Qwen Code to recover memory');137 expect(report).toContain('capture a heap snapshot');138 });139 140 it('surfaces large non-heap memory gaps separately from V8 heap pressure', () => {141 const report = formatMemoryDiagnostics({142 generatedAt: '2026-05-15T12:00:00.000Z',143 process: {144 pid: 123,145 nodeVersion: 'v22.0.0',146 platform: 'linux',147 arch: 'x64',148 uptimeSeconds: 120,149 },150 memory: {151 rss: 1800 * 1024 * 1024,152 heapTotal: 500 * 1024 * 1024,153 heapUsed: 300 * 1024 * 1024,154 external: 900 * 1024 * 1024,155 arrayBuffers: 300 * 1024 * 1024,156 },157 v8: {158 heapStatistics: {159 heap_size_limit: 4096 * 1024 * 1024,160 total_available_size: 3000 * 1024 * 1024,161 },162 heapSpaces: [],163 },164 activeHandles: { count: 3, unavailable: false },165 activeRequests: { count: 1, unavailable: false },166 });167 168 expect(report).toContain('Status: warn');169 expect(report).toContain('RSS / heap-total gap: 1300.0 MiB');170 expect(report).toContain('Non-heap memory is high');171 expect(report).toContain(172 'large tool results, buffers, or native allocations',173 );174 });175 176 it('writes heap snapshots to a diagnostics directory with stable filenames', () => {177 const outputDir = path.join(os.tmpdir(), 'qwen-memory-diagnostics-test');178 const writtenPath = writeMemoryHeapSnapshot({179 outputDir,180 now: new Date('2026-05-15T12:00:00.000Z'),181 writeSnapshot: (filePath) => {182 fs.writeFileSync(filePath, 'snapshot');183 return filePath;184 },185 });186 187 expect(writtenPath).toBe(188 path.join(189 outputDir,190 `qwen-code-heap-${process.pid}-2026-05-15T12-00-00-000Z.heapsnapshot`,191 ),192 );193 });194 195 it('refuses heap snapshots when estimated heap dump would leave little free disk', () => {196 const outputDir = path.join(197 os.tmpdir(),198 `qwen-memory-diagnostics-disk-${process.pid}`,199 );200 fs.rmSync(outputDir, { recursive: true, force: true });201 202 try {203 expect(() =>204 writeMemoryHeapSnapshot({205 outputDir,206 writeSnapshot: (filePath) => {207 fs.writeFileSync(filePath, 'snapshot');208 return filePath;209 },210 estimateSnapshotBytes: () => 900,211 getAvailableBytes: () => 1000,212 minFreeBytesAfterSnapshot: 200,213 }),214 ).toThrow('Insufficient free disk space');215 expect(fs.readdirSync(outputDir)).toHaveLength(0);216 } finally {217 fs.rmSync(outputDir, { recursive: true, force: true });218 }219 });220 221 it('rate-limits repeated heap snapshot writes in the same directory', () => {222 const outputDir = path.join(223 os.tmpdir(),224 `qwen-memory-diagnostics-rate-limit-${process.pid}`,225 );226 fs.rmSync(outputDir, { recursive: true, force: true });227 228 try {229 const writeSnapshot = (filePath: string) => {230 fs.writeFileSync(filePath, 'snapshot');231 return filePath;232 };233 234 writeMemoryHeapSnapshot({235 outputDir,236 now: new Date('2026-05-15T12:00:00.000Z'),237 writeSnapshot,238 });239 240 expect(() =>241 writeMemoryHeapSnapshot({242 outputDir,243 now: new Date('2026-05-15T12:00:30.000Z'),244 writeSnapshot,245 }),246 ).toThrow('Heap snapshot rate limit');247 } finally {248 fs.rmSync(outputDir, { recursive: true, force: true });249 }250 });251 252 it('keeps only the newest heap snapshots after writing', () => {253 const outputDir = path.join(254 os.tmpdir(),255 `qwen-memory-diagnostics-cleanup-${process.pid}`,256 );257 fs.rmSync(outputDir, { recursive: true, force: true });258 fs.mkdirSync(outputDir, { recursive: true });259 260 const oldSnapshot = path.join(261 outputDir,262 `qwen-code-heap-${process.pid}-2026-05-15T11-00-00-000Z.heapsnapshot`,263 );264 const newerSnapshot = path.join(265 outputDir,266 `qwen-code-heap-${process.pid}-2026-05-15T11-30-00-000Z.heapsnapshot`,267 );268 fs.writeFileSync(oldSnapshot, 'old');269 fs.writeFileSync(newerSnapshot, 'newer');270 271 const writtenPath = writeMemoryHeapSnapshot({272 outputDir,273 now: new Date('2026-05-15T12:00:00.000Z'),274 maxSnapshots: 2,275 writeSnapshot: (filePath) => {276 fs.writeFileSync(filePath, 'snapshot');277 return filePath;278 },279 });280 281 try {282 expect(fs.existsSync(oldSnapshot)).toBe(false);283 expect(fs.existsSync(newerSnapshot)).toBe(true);284 expect(fs.existsSync(writtenPath)).toBe(true);285 } finally {286 fs.rmSync(outputDir, { recursive: true, force: true });287 }288 });289 290 it('orders heap snapshot cleanup by modification time across process ids', () => {291 const outputDir = path.join(292 os.tmpdir(),293 `qwen-memory-diagnostics-mtime-cleanup-${process.pid}`,294 );295 fs.rmSync(outputDir, { recursive: true, force: true });296 fs.mkdirSync(outputDir, { recursive: true });297 298 const newerLowPidSnapshot = path.join(299 outputDir,300 'qwen-code-heap-9-2026-05-15T12-30-00-000Z.heapsnapshot',301 );302 const olderHighPidSnapshot = path.join(303 outputDir,304 'qwen-code-heap-12345-2026-05-15T12-00-00-000Z.heapsnapshot',305 );306 fs.writeFileSync(newerLowPidSnapshot, 'newer');307 fs.writeFileSync(olderHighPidSnapshot, 'older');308 fs.utimesSync(309 olderHighPidSnapshot,310 new Date('2026-05-15T12:00:00.000Z'),311 new Date('2026-05-15T12:00:00.000Z'),312 );313 fs.utimesSync(314 newerLowPidSnapshot,315 new Date('2026-05-15T12:30:00.000Z'),316 new Date('2026-05-15T12:30:00.000Z'),317 );318 319 const writtenPath = writeMemoryHeapSnapshot({320 outputDir,321 now: new Date('2026-05-15T13:00:00.000Z'),322 maxSnapshots: 2,323 writeSnapshot: (filePath) => {324 fs.writeFileSync(filePath, 'snapshot');325 return filePath;326 },327 });328 329 try {330 expect(fs.existsSync(olderHighPidSnapshot)).toBe(false);331 expect(fs.existsSync(newerLowPidSnapshot)).toBe(true);332 expect(fs.existsSync(writtenPath)).toBe(true);333 } finally {334 fs.rmSync(outputDir, { recursive: true, force: true });335 }336 });337 338 it('uses filename timestamps to break equal-mtime cleanup ties', () => {339 const outputDir = path.join(340 os.tmpdir(),341 `qwen-memory-diagnostics-equal-mtime-cleanup-${process.pid}`,342 );343 fs.rmSync(outputDir, { recursive: true, force: true });344 fs.mkdirSync(outputDir, { recursive: true });345 346 const olderSnapshot = path.join(347 outputDir,348 'qwen-code-heap-1-2026-05-15T11-00-00-000Z.heapsnapshot',349 );350 const newerSnapshot = path.join(351 outputDir,352 'qwen-code-heap-1-2026-05-15T12-00-00-000Z.heapsnapshot',353 );354 fs.writeFileSync(olderSnapshot, 'older');355 fs.writeFileSync(newerSnapshot, 'newer');356 const sameMtime = new Date('2026-05-15T12:00:00.000Z');357 fs.utimesSync(olderSnapshot, sameMtime, sameMtime);358 fs.utimesSync(newerSnapshot, sameMtime, sameMtime);359 360 const writtenPath = writeMemoryHeapSnapshot({361 outputDir,362 now: new Date('2026-05-15T13:00:00.000Z'),363 maxSnapshots: 2,364 writeSnapshot: (filePath) => {365 fs.writeFileSync(filePath, 'snapshot');366 return filePath;367 },368 });369 370 try {371 expect(fs.existsSync(olderSnapshot)).toBe(false);372 expect(fs.existsSync(newerSnapshot)).toBe(true);373 expect(fs.existsSync(writtenPath)).toBe(true);374 } finally {375 fs.rmSync(outputDir, { recursive: true, force: true });376 }377 });378 379 it('keeps successful heap snapshot writes when cleanup fails', () => {380 const outputDir = path.join(381 os.tmpdir(),382 `qwen-memory-diagnostics-cleanup-fail-${process.pid}`,383 );384 fs.rmSync(outputDir, { recursive: true, force: true });385 fs.mkdirSync(outputDir, { recursive: true });386 const brokenSymlink = path.join(387 outputDir,388 'qwen-code-heap-999-2026-05-15T11-00-00-000Z.heapsnapshot',389 );390 fs.symlinkSync('missing-target.heapsnapshot', brokenSymlink);391 392 try {393 const writtenPath = writeMemoryHeapSnapshot({394 outputDir,395 now: new Date('2026-05-15T12:00:00.000Z'),396 maxSnapshots: 1,397 writeSnapshot: (filePath) => {398 fs.writeFileSync(filePath, 'snapshot');399 return filePath;400 },401 });402 403 expect(fs.existsSync(writtenPath)).toBe(true);404 expect(() => fs.lstatSync(brokenSymlink)).toThrow();405 } finally {406 fs.rmSync(outputDir, { recursive: true, force: true });407 }408 });409 410 it('removes partial heap snapshot files after a failed write', () => {411 const outputDir = path.join(412 os.tmpdir(),413 `qwen-memory-diagnostics-partial-${process.pid}`,414 );415 fs.rmSync(outputDir, { recursive: true, force: true });416 let partialPath = '';417 418 try {419 expect(() =>420 writeMemoryHeapSnapshot({421 outputDir,422 now: new Date('2026-05-15T12:00:00.000Z'),423 writeSnapshot: (filePath) => {424 partialPath = filePath;425 fs.writeFileSync(filePath, 'partial');426 throw new Error('write failed');427 },428 }),429 ).toThrow('write failed');430 431 expect(fs.existsSync(partialPath)).toBe(false);432 } finally {433 fs.rmSync(outputDir, { recursive: true, force: true });434 }435 });436 437 it('creates heap snapshot directories and files with private permissions', () => {438 const outputDir = path.join(439 os.tmpdir(),440 `qwen-memory-diagnostics-private-${process.pid}`,441 );442 fs.rmSync(outputDir, { recursive: true, force: true });443 444 const writtenPath = writeMemoryHeapSnapshot({445 outputDir,446 now: new Date('2026-05-15T12:00:00.000Z'),447 writeSnapshot: (filePath) => {448 fs.writeFileSync(filePath, 'snapshot');449 return filePath;450 },451 });452 453 try {454 expect(fs.existsSync(outputDir)).toBe(true);455 expect(fs.existsSync(writtenPath)).toBe(true);456 if (process.platform !== 'win32') {457 expect(fs.statSync(outputDir).mode & 0o777).toBe(0o700);458 expect(fs.statSync(writtenPath).mode & 0o777).toBe(0o600);459 }460 } finally {461 fs.rmSync(outputDir, { recursive: true, force: true });462 }463 });464 465 it('refuses heap snapshot writes when free disk space cannot be read', async () => {466 const outputDir = path.join(467 os.tmpdir(),468 `qwen-memory-diagnostics-statfs-fallback-${process.pid}`,469 );470 fs.rmSync(outputDir, { recursive: true, force: true });471 vi.resetModules();472 const statfsSync = vi.fn(() => {473 throw new Error('statfs unavailable');474 });475 vi.doMock('node:fs', async (importOriginal) => {476 const actual = await importOriginal<typeof import('node:fs')>();477 return { ...actual, statfsSync };478 });479 const { writeMemoryHeapSnapshot: writeWithMockedFs } = await import(480 './memoryDiagnostics.js'481 );482 483 try {484 expect(() =>485 writeWithMockedFs({486 outputDir,487 now: new Date('2026-05-15T12:00:00.000Z'),488 writeSnapshot: (filePath) => {489 fs.writeFileSync(filePath, 'snapshot');490 return filePath;491 },492 }),493 ).toThrow('Unable to check available disk space');494 495 expect(statfsSync).toHaveBeenCalledWith(outputDir);496 expect(fs.readdirSync(outputDir)).toHaveLength(0);497 } finally {498 fs.rmSync(outputDir, { recursive: true, force: true });499 }500 });501 502 it('falls back to process heap total when V8 heap statistics are unavailable for snapshot sizing', async () => {503 const outputDir = path.join(504 os.tmpdir(),505 `qwen-memory-diagnostics-estimate-fallback-${process.pid}`,506 );507 fs.rmSync(outputDir, { recursive: true, force: true });508 vi.resetModules();509 const getHeapStatistics = vi.fn(() => {510 throw new Error('heap statistics unavailable');511 });512 vi.doMock('node:v8', async (importOriginal) => {513 const actual = await importOriginal<typeof import('node:v8')>();514 return { ...actual, getHeapStatistics };515 });516 const { writeMemoryHeapSnapshot: writeWithMockedV8 } = await import(517 './memoryDiagnostics.js'518 );519 vi.spyOn(process, 'memoryUsage').mockReturnValue({520 rss: 40,521 heapTotal: 100,522 heapUsed: 50,523 external: 10,524 arrayBuffers: 5,525 });526 527 try {528 expect(() =>529 writeWithMockedV8({530 outputDir,531 now: new Date('2026-05-15T12:00:00.000Z'),532 writeSnapshot: (filePath) => {533 fs.writeFileSync(filePath, 'snapshot');534 return filePath;535 },536 getAvailableBytes: () => 350,537 minFreeBytesAfterSnapshot: 60,538 }),539 ).toThrow('Insufficient free disk space');540 expect(getHeapStatistics).toHaveBeenCalled();541 } finally {542 fs.rmSync(outputDir, { recursive: true, force: true });543 }544 });545 546 it('uses the default sample count for non-positive sample counts', async () => {547 const samples = await collectMemoryPressureSamples({548 sampleCount: 0,549 intervalMs: 0,550 now: () => new Date('2026-05-15T12:00:00.000Z'),551 memoryUsage: () => ({552 rss: 100,553 heapTotal: 80,554 heapUsed: 40,555 external: 5,556 arrayBuffers: 2,557 }),558 wait: async () => {},559 });560 561 expect(samples).toHaveLength(3);562 });563 564 it('marks V8 diagnostics as warning when heap statistics are unavailable', () => {565 const report = formatMemoryDiagnostics({566 generatedAt: '2026-05-15T12:00:00.000Z',567 process: {568 pid: 123,569 nodeVersion: 'v22.0.0',570 platform: 'linux',571 arch: 'x64',572 uptimeSeconds: 120,573 },574 memory: {575 rss: 100 * 1024 * 1024,576 heapTotal: 80 * 1024 * 1024,577 heapUsed: 40 * 1024 * 1024,578 external: 5 * 1024 * 1024,579 arrayBuffers: 2 * 1024 * 1024,580 },581 v8: {582 unavailable: true,583 heapSpaces: [],584 },585 activeHandles: { count: 3, unavailable: false },586 activeRequests: { count: 1, unavailable: false },587 });588 589 expect(report).toContain('Status: warn');590 expect(report).toContain('V8 heap statistics are unavailable');591 expect(report).not.toContain(592 'No immediate memory pressure signals detected.',593 );594 });595 596 it('surfaces high active handle counts with actionable recommendations', () => {597 const report = formatMemoryDiagnostics({598 generatedAt: '2026-05-15T12:00:00.000Z',599 process: {600 pid: 123,601 nodeVersion: 'v22.0.0',602 platform: 'linux',603 arch: 'x64',604 uptimeSeconds: 120,605 },606 memory: {607 rss: 100 * 1024 * 1024,608 heapTotal: 80 * 1024 * 1024,609 heapUsed: 40 * 1024 * 1024,610 external: 5 * 1024 * 1024,611 arrayBuffers: 2 * 1024 * 1024,612 },613 v8: {614 heapStatistics: {615 heap_size_limit: 4096 * 1024 * 1024,616 },617 heapSpaces: [],618 },619 activeHandles: { count: 1000, unavailable: false },620 activeRequests: { count: 1, unavailable: false },621 });622 623 expect(report).toContain('Active handle count is high');624 expect(report).toContain('MCP servers, watchers, or streaming sessions');625 });626 627 it('reports high heap pressure only with a finite positive heap limit', () => {628 const baseDiagnostics = {629 generatedAt: '2026-05-15T12:00:00.000Z',630 process: {631 pid: 123,632 nodeVersion: 'v22.0.0',633 platform: 'linux' as const,634 arch: 'x64',635 uptimeSeconds: 120,636 },637 memory: {638 rss: 100,639 heapTotal: 80,640 heapUsed: 90,641 external: 5,642 arrayBuffers: 2,643 },644 v8: {645 heapSpaces: [],646 },647 activeHandles: { count: 3, unavailable: false },648 activeRequests: { count: 1, unavailable: false },649 };650 651 expect(652 isHighHeapPressure({653 ...baseDiagnostics,654 v8: { ...baseDiagnostics.v8, heapStatistics: { heap_size_limit: 0 } },655 }),656 ).toBe(false);657 expect(658 isHighHeapPressure({659 ...baseDiagnostics,660 v8: { ...baseDiagnostics.v8, heapStatistics: {} },661 }),662 ).toBe(false);663 expect(isHighHeapPressure(baseDiagnostics)).toBe(false);664 expect(665 isHighHeapPressure({666 ...baseDiagnostics,667 v8: {668 ...baseDiagnostics.v8,669 heapStatistics: { heap_size_limit: 100 },670 },671 }),672 ).toBe(true);673 });674 675 it('collects repeated memory pressure samples with waits between samples', async () => {676 const waits: number[] = [];677 const memoryUsages = [678 {679 rss: 100 * 1024 * 1024,680 heapTotal: 80 * 1024 * 1024,681 heapUsed: 40 * 1024 * 1024,682 external: 5 * 1024 * 1024,683 arrayBuffers: 2 * 1024 * 1024,684 },685 {686 rss: 130 * 1024 * 1024,687 heapTotal: 90 * 1024 * 1024,688 heapUsed: 60 * 1024 * 1024,689 external: 6 * 1024 * 1024,690 arrayBuffers: 3 * 1024 * 1024,691 },692 {693 rss: 150 * 1024 * 1024,694 heapTotal: 100 * 1024 * 1024,695 heapUsed: 70 * 1024 * 1024,696 external: 7 * 1024 * 1024,697 arrayBuffers: 4 * 1024 * 1024,698 },699 ];700 701 const samples = await collectMemoryPressureSamples({702 sampleCount: 3,703 intervalMs: 25,704 now: () => new Date('2026-05-15T12:00:00.000Z'),705 memoryUsage: () => memoryUsages.shift()!,706 wait: async (ms) => {707 waits.push(ms);708 },709 });710 711 expect(samples).toHaveLength(3);712 expect(waits).toEqual([25, 25]);713 expect(samples[0]).toMatchObject({ index: 1, rss: 100 * 1024 * 1024 });714 expect(samples[2]).toMatchObject({ index: 3, heapUsed: 70 * 1024 * 1024 });715 });716 717 it('stops collecting memory pressure samples when aborted', async () => {718 const abortController = new AbortController();719 const samples = await collectMemoryPressureSamples({720 sampleCount: 3,721 intervalMs: 25,722 signal: abortController.signal,723 now: () => new Date('2026-05-15T12:00:00.000Z'),724 memoryUsage: () => {725 abortController.abort();726 return {727 rss: 100,728 heapTotal: 80,729 heapUsed: 40,730 external: 5,731 arrayBuffers: 2,732 };733 },734 wait: async () => {735 throw new Error('should not wait after abort');736 },737 });738 739 expect(samples).toHaveLength(1);740 });741 742 it('formats single memory pressure sample deltas as unavailable', () => {743 const report = formatMemoryPressureSamples([744 {745 index: 1,746 timestamp: '2026-05-15T12:00:00.000Z',747 rss: 100 * 1024 * 1024,748 heapTotal: 80 * 1024 * 1024,749 heapUsed: 40 * 1024 * 1024,750 external: 5 * 1024 * 1024,751 arrayBuffers: 2 * 1024 * 1024,752 },753 ]);754 755 expect(report).toContain('Sample count: 1');756 expect(report).toContain('RSS delta: unavailable');757 expect(report).toContain('Heap used delta: unavailable');758 });759 760 it('formats memory pressure sample deltas', () => {761 const report = formatMemoryPressureSamples([762 {763 index: 1,764 timestamp: '2026-05-15T12:00:00.000Z',765 rss: 100 * 1024 * 1024,766 heapTotal: 80 * 1024 * 1024,767 heapUsed: 40 * 1024 * 1024,768 external: 5 * 1024 * 1024,769 arrayBuffers: 2 * 1024 * 1024,770 },771 {772 index: 2,773 timestamp: '2026-05-15T12:00:01.000Z',774 rss: 130 * 1024 * 1024,775 heapTotal: 90 * 1024 * 1024,776 heapUsed: 60 * 1024 * 1024,777 external: 6 * 1024 * 1024,778 arrayBuffers: 3 * 1024 * 1024,779 },780 ]);781 782 expect(report).toContain('Memory pressure samples');783 expect(report).toContain('Sample count: 2');784 expect(report).toContain('RSS delta: 30.0 MiB');785 expect(report).toContain('Heap used delta: 20.0 MiB');786 expect(report).toContain('#2 2026-05-15T12:00:01.000Z');787 });788});789 