v0.32.6_fix2

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-14 11:51:37 +02:00
1 parent e1ac899ec0
commit fc8711d4d8
39 files changed
+695 -232

No files matched your search

+126
View File
@@ -0,0 +1,126 @@
// Counter-derived chart regression: pauses must not become fake 0/s samples.
// Real Cache / Transform chart functions run against simulated DOM and clock.
import assert from 'node:assert/strict';
import fs from 'node:fs';
import vm from 'node:vm';
const samplingSource = fs.readFileSync('src/admin/web/app/rate-sampling.js', 'utf8');
const panels = {
cache: {
path: 'src/admin/web/cache-panel.js', stop: ' function configure()',
counters: (hit, miss) => ({ hits: hit, misses: miss, bypasses: 0 }),
graph: 'cache-live-hit-line', rate: 'cache-live-hit-rate',
keys: ['hits', 'misses', 'bypasses'], seconds: 5,
},
transform: {
path: 'src/admin/web/transform-panel.js', stop: ' function countersFor(data)',
counters: (hit, miss) => ({ applied: hit, cache_hits: miss, skipped: 0, errors: 0 }),
graph: 'transform-live-applied-line', rate: 'transform-live-applied-rate',
keys: ['applied', 'cache_hits', 'skipped', 'errors'], seconds: 3,
},
};
function fixture(name, config) {
const source = fs.readFileSync(config.path, 'utf8');
const end = source.indexOf(config.stop);
assert(end > 0, `unable to isolate ${name} live chart`);
const documentListeners = new Map(), windowListeners = new Map(), nodes = new Map();
let now = 1000;
const node = id => {
if (!nodes.has(id)) nodes.set(id, {
value: id.endsWith('-scope') ? '*' : '', textContent: '',
attrs: {}, setAttribute(key, value) { this.attrs[key] = value; },
});
return nodes.get(id);
};
const document = {
hidden: false,
addEventListener(name, listener) { documentListeners.set(name, listener); },
};
const window = {
addEventListener(name, listener) { windowListeners.set(name, listener); },
rustProxyRefresh: { current: () => ({ overview_seconds: 2, cache_seconds: config.seconds }) },
};
const ctx = {
document, window, $: node, performance: { now: () => now },
sparklinePoints: samples => samples.join(','),
};
vm.createContext(ctx);
vm.runInContext(samplingSource, ctx);
vm.runInContext(source.slice(0, end) + '\nwindow.liveChartTest = { renderLiveChart, resetLiveChart, liveChartSamples };\n})();', ctx);
const chart = window.liveChartTest;
const line = () => node(config.graph).attrs.points;
const rate = () => node(config.rate).textContent;
const length = () => chart.liveChartSamples[config.keys[0]].length;
const sample = (stamp, hits, misses) => {
now = stamp;
chart.renderLiveChart(config.counters(hits, misses));
};
return { ctx, chart, line, rate, length, sample, document, documentListeners, windowListeners };
}
for (const [name, config] of Object.entries(panels)) {
const f = fixture(name, config);
f.sample(1000, 100, 50); // no deltas exist before the first observation
assert.equal(f.length(), 0, `${name}: first reading is a baseline, not 0/s`);
assert.equal(f.rate(), '-');
f.sample(1000 + config.seconds * 1000, 130, 65);
assert.equal(f.rate(), `${(30 / config.seconds).toFixed(1)}/s`);
assert.equal(f.length(), 1);
f.sample(1000 + config.seconds * 2000, 130, 65);
assert.equal(f.rate(), '0.0/s', `${name}: real idle interval is still zero`);
assert.equal(f.length(), 2);
const graphBefore = f.line();
const delayed = 1000 + config.seconds * 2000 + 120000;
f.sample(delayed, 500, 200);
assert.equal(f.rate(), '-', `${name}: long pause cannot be averaged into a misleading rate`);
assert.equal(f.length(), 2, `${name}: do not append fake drop`);
assert.equal(f.line(), graphBefore, `${name}: keep old chart path`);
f.sample(delayed + config.seconds * 1000, 530, 215);
assert.equal(f.rate(), `${(30 / config.seconds).toFixed(1)}/s`, `${name}: next fresh interval resumes`);
assert.equal(f.length(), 3);
f.document.hidden = true;
f.documentListeners.get('visibilitychange')?.();
f.document.hidden = false;
f.documentListeners.get('visibilitychange')?.();
const afterBackground = delayed + config.seconds * 1000 + 200;
f.sample(afterBackground, 533, 219);
assert.equal(f.length(), 3, `${name}: even a short background hide must not append a rate`);
assert.equal(f.rate(), '-');
f.sample(afterBackground + config.seconds * 1000, 533, 219);
assert.equal(f.rate(), '0.0/s', `${name}: genuine zero after resume`);
assert.equal(f.length(), 4);
f.windowListeners.get('pagehide')?.();
f.sample(afterBackground + config.seconds * 2000, 540, 220);
assert.equal(f.length(), 4, `${name}: pagehide / BFCache must rebase`);
f.sample(afterBackground + config.seconds * 3000, 550, 230);
assert.equal(f.length(), 5);
f.sample(afterBackground + config.seconds * 4000, 1, 1);
assert.equal(f.length(), 5, `${name}: counters reset must not be interpreted as 0/s`);
f.sample(afterBackground + config.seconds * 5000, 3, 3);
assert.equal(f.length(), 6, `${name}: counter reset recovery`);
}
// All remaining rate charts share this exact continuity check, including
// configured 30-second Cache refresh (not falsely treated as a pause).
{
const listeners = new Map(), document = { hidden: false, addEventListener(key, fn) { listeners.set(key, fn); } };
const context = { window: {}, document };
vm.createContext(context);
vm.runInContext(samplingSource, context);
const sample = vm.runInContext('rustProxyRateSampling', context);
let baseline = sample.difference(null, [100], 1000, 30).next;
let value = sample.difference(baseline, [130], 31000, 30);
assert.equal(value.deltas[0], 30, '30-second chart interval is valid');
baseline = value.next;
value = sample.difference(baseline, [200], 124000, 30);
assert.equal(value.deltas, null, 'large delay is a gap');
document.hidden = true;
listeners.get('visibilitychange')?.();
document.hidden = false;
assert.equal(sample.continuous(value.next, 124001, 30), false, 'hide invalidates even a short elapsed period');
}
console.log('PASS: Cache/Transform history survives hidden tabs, long pauses, BFCache and counter resets; real 0/s and configured cadence remain intact.');