mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-18 02:35:41 +02:00
63 lines
3.2 KiB
JavaScript
63 lines
3.2 KiB
JavaScript
// Run: node --test electron/memoryRelief.test.js
|
|
//
|
|
// ENG-320: memory pressure was detected and only reported; the observed end state is macOS killing
|
|
// the app with no trace, once caught live mid-capturePage. These pin the relief contract: enter at
|
|
// the cap, act ONCE per episode, gate thumbnails while under pressure, exit below 80% with
|
|
// hysteresis so a session hovering at the line cannot thrash caches, and the wire into the sensor
|
|
// and capture-page actually exists (an unconsulted flag is the ENG-284 anti-pattern).
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const relief = require('./memoryRelief');
|
|
|
|
test.beforeEach(() => relief.resetMemoryRelief());
|
|
|
|
test('crossing the cap enters pressure and fires the action set once', () => {
|
|
let cleared = 0;
|
|
relief.initMemoryRelief({ clearCaches: () => { cleared += 1; } });
|
|
assert.equal(relief.updateMemoryPressure(2999, 3000), false);
|
|
assert.equal(relief.underMemoryPressure(), false);
|
|
assert.equal(relief.updateMemoryPressure(3000, 3000), true);
|
|
assert.equal(relief.underMemoryPressure(), true);
|
|
assert.equal(cleared, 1);
|
|
// Staying above the cap must not thrash: one action set per episode.
|
|
relief.updateMemoryPressure(3400, 3000);
|
|
relief.updateMemoryPressure(3600, 3000);
|
|
assert.equal(cleared, 1, 'clearing caches per sample would churn the disk the way ENG-247 did');
|
|
});
|
|
|
|
test('exit needs 80% hysteresis, and a new episode acts again', () => {
|
|
let cleared = 0;
|
|
relief.initMemoryRelief({ clearCaches: () => { cleared += 1; } });
|
|
relief.updateMemoryPressure(3200, 3000);
|
|
assert.equal(relief.updateMemoryPressure(2500, 3000), false, '2500 is above 80% of 3000; still under pressure');
|
|
assert.equal(relief.underMemoryPressure(), true);
|
|
assert.equal(relief.updateMemoryPressure(2300, 3000), true, 'below 2400 clears');
|
|
assert.equal(relief.underMemoryPressure(), false);
|
|
relief.updateMemoryPressure(3100, 3000);
|
|
assert.equal(cleared, 2, 'a genuine second episode earns a second cache clear');
|
|
});
|
|
|
|
test('a throwing action never breaks the sensor path', () => {
|
|
relief.initMemoryRelief({ clearCaches: () => { throw new Error('disk gone'); } });
|
|
assert.doesNotThrow(() => relief.updateMemoryPressure(3200, 3000));
|
|
assert.equal(relief.underMemoryPressure(), true, 'the STATE must flip even when actions fail');
|
|
});
|
|
|
|
test('no actions wired is safe (early boot)', () => {
|
|
assert.doesNotThrow(() => relief.updateMemoryPressure(9000, 3000));
|
|
assert.equal(relief.underMemoryPressure(), true);
|
|
});
|
|
|
|
test('the sensor feeds it and capture-page consults it', () => {
|
|
const sensor = fs.readFileSync(path.join(__dirname, 'memorySensor.js'), 'utf8');
|
|
assert.match(sensor, /updateMemoryPressure\(mb, TOTAL_MB_CAP\)/, 'unwired relief is telemetry with extra steps');
|
|
const main = fs.readFileSync(path.join(__dirname, 'main.js'), 'utf8');
|
|
const start = main.indexOf("ipcMain.handle('capture-page'");
|
|
const beforeComposite = main.slice(start, main.indexOf('capturePage(', start));
|
|
assert.match(beforeComposite, /underMemoryPressure\(\)\) return null/, 'the gate must run BEFORE the composite, the observed death site');
|
|
assert.match(main, /initMemoryRelief\(/, 'actions must be wired at boot');
|
|
});
|