aere-quantum/crypto-inventory/test/proba-timp.mjs

73 lines
4.6 KiB
JavaScript

// Proba de cost: inventarul trebuie sa ramana LINIAR pe fisiere facute anume (2026-09-29, revizuirea adversariala). Formele vechi
// ale cautarii PEM, ale literalului regex JS, ale docstring-ului Python, ale rezolvarii variabilelor si ale variabilei atribuite
// in Java costau patratic: un singur fisier de 1 MB (marimea maxima implicita) tinea scanarea minute intregi.
// Fiecare caz se scaneaza la doua marimi, a doua de 4 ori prima (64 si 256 KB; PEM 256 KB si 1 MB), fiecare intr-un proces
// separat cu termen; liniar inseamna ~4x, patratic ~16x. Verdict pe caz: VERDE daca t2 <= 6 * t1 + 250 ms si t2 < 10 s; ROSU
// altfel (si la depasirea termenului).
// node test/proba-timp.mjs [--doar caz1,caz2] -> 0 toate verzi, 1 unul rosu, 2 STRICAT (o masurare nu a pornit)
// Modulul masurat se schimba cu CRYPTO_INVENTORY_MODULE=<cale>/inventar.mjs (asa il foloseste controlul negativ).
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
import { join, resolve, dirname } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
const AICI = dirname(fileURLToPath(import.meta.url));
const CALE_MODUL = process.env.CRYPTO_INVENTORY_MODULE ? resolve(process.env.CRYPTO_INVENTORY_MODULE) : join(AICI, '..', 'inventar.mjs');
const TERMEN_MS = 20000;
// unitatea se repeta pana la marime; '@' se inlocuieste cu un contor (nume distincte)
export const CAZURI = {
// pem: cautarea sfarsitului e nativa (indexOf), deci forma patratica se vede abia spre marimea maxima implicita
'pem.txt': ['-----BEGIN CERTIFICATE-----AAAA\n', 256, 1024],
'py-triplu.py': 'x="""a"""+',
'py-linie.py': 'hashlib.new(x)+',
'js-regex.js': '=/[',
'js-linie.js': 'crypto.createHash(x)+',
'js-func.js': 'crypto.createHash(q)\nfunction (',
'java-linie.java': 'Cipher.getInstance(x)+',
'java-kpg.java': 'k = KeyPairGenerator.getInstance("EC")+',
};
const marimi = (nume) => (Array.isArray(CAZURI[nume]) ? CAZURI[nume].slice(1) : [64, 256]);
const unitate = (nume) => (Array.isArray(CAZURI[nume]) ? CAZURI[nume][0] : CAZURI[nume]);
const genereaza = (unit, kb) => unit.repeat(Math.max(1, Math.floor((kb * 1024) / unit.length)));
const arg = (f) => { const i = process.argv.indexOf(f); return i >= 0 ? process.argv[i + 1] : null; };
if (arg('--caz')) {
// copilul: scaneaza un singur fisier si tipareste milisecundele
const nume = arg('--caz'); const kb = Number(arg('--kb'));
const inv = await import(pathToFileURL(CALE_MODUL).href);
const d = mkdtempSync(join(tmpdir(), 'ci-timp-'));
try {
writeFileSync(join(d, nume), genereaza(unitate(nume), kb));
const t0 = process.hrtime.bigint();
const rez = inv.scan(d);
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
if (rez.stats.filesSeen !== 1 || rez.stats.codeFilesTotal + rez.stats.textFilesPemOnly !== 1) throw new Error('fisierul nu a fost citit');
console.log(JSON.stringify({ ms }));
} finally { rmSync(d, { recursive: true, force: true }); }
} else {
const doar = arg('--doar') ? arg('--doar').split(',') : Object.keys(CAZURI);
const masoara = (nume, kb) => {
const r = spawnSync(process.execPath, [fileURLToPath(import.meta.url), '--caz', nume, '--kb', String(kb)], { encoding: 'utf8', timeout: TERMEN_MS, env: process.env });
if (r.error && r.error.code === 'ETIMEDOUT') return { termen: true };
try { return { ms: JSON.parse((r.stdout || '').trim().split('\n').pop()).ms }; } catch { return { stricat: `cod ${r.status}: ${(r.stderr || '').trim().split('\n').slice(-1)[0]}` }; }
};
let rosii = 0, stricate = 0;
for (const nume of doar) {
if (!CAZURI[nume]) { stricate++; console.log(`STRICAT ${nume}: caz necunoscut`); continue; }
const [k1, k2] = marimi(nume);
const a = masoara(nume, k1);
const b = a.ms !== undefined ? masoara(nume, k2) : a.termen ? {} : { stricat: `nemasurat (${k1} KB a cazut)` };
if (a.stricat || b.stricat) { stricate++; console.log(`STRICAT ${nume}: ${a.stricat || b.stricat}`); continue; }
if (a.termen || b.termen) { rosii++; console.log(`ROSU ${nume}: peste ${TERMEN_MS / 1000} s la ${a.termen ? k1 : k2} KB`); continue; }
const ok = b.ms <= 6 * a.ms + 250 && b.ms < 10000;
if (!ok) rosii++;
console.log(`${ok ? 'VERDE ' : 'ROSU '} ${nume.padEnd(16)} ${String(k1).padStart(4)} KB ${a.ms.toFixed(0).padStart(6)} ms | ${String(k2).padStart(4)} KB ${b.ms.toFixed(0).padStart(6)} ms x${(b.ms / Math.max(a.ms, 1)).toFixed(1)}`);
}
console.log(`\ncostul inventarului: ${doar.length - rosii - stricate}/${doar.length} liniare, ${rosii} rosii, ${stricate} stricate (modul: ${CALE_MODUL})`);
process.exitCode = stricate ? 2 : rosii ? 1 : 0;
}