From edbd66df5299bd32d3dc2d1583aa59ec5ee0d56e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 06:48:14 +0000 Subject: [PATCH] feat: add privacy-preserving telemetry and error aggregation - Update smoke-test.mjs to output structured summary with logs - Update telemetry-aggregator.mjs to collect from unit, smoke, and load tests - Update run-unit-tests.mjs to log output to artifacts/test_unit.log - Ensure PII sanitization in telemetry reports --- scripts/agent/smoke-test.mjs | 8 ++- scripts/agent/telemetry-aggregator.mjs | 73 ++++++++++++++++++++++++-- scripts/run-unit-tests.mjs | 17 +++++- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/scripts/agent/smoke-test.mjs b/scripts/agent/smoke-test.mjs index 85ec69d4..3206573b 100644 --- a/scripts/agent/smoke-test.mjs +++ b/scripts/agent/smoke-test.mjs @@ -38,7 +38,7 @@ import http from 'http'; const ARTIFACTS_DIR = 'artifacts'; const LOG_FILE = path.join(ARTIFACTS_DIR, `smoke-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.log`); -const REPORT_FILE = path.join(ARTIFACTS_DIR, 'smoke-report.json'); +const REPORT_FILE = path.join(ARTIFACTS_DIR, `smoke-summary-${new Date().toISOString().replace(/[:.]/g, '-')}.json`); const UI_SCREENSHOT = path.join(ARTIFACTS_DIR, 'smoke-ui.png'); if (!fs.existsSync(ARTIFACTS_DIR)) { @@ -285,12 +285,16 @@ async function runSmokeTest() { } catch (error) { log(`Smoke Test Failed: ${error.stack}`, 'ERROR'); - summary.status = "FAIL"; + summary.status = "failure"; summary.error = error.message; exitCode = 1; } finally { // --- Write Report --- try { + if (fs.existsSync(LOG_FILE)) { + const logs = fs.readFileSync(LOG_FILE, 'utf8').split('\n').filter(Boolean); + summary.logs = logs; + } fs.writeFileSync(REPORT_FILE, JSON.stringify(summary, null, 2)); log(`Summary written to ${REPORT_FILE}`); } catch (e) { diff --git a/scripts/agent/telemetry-aggregator.mjs b/scripts/agent/telemetry-aggregator.mjs index 5ec7401a..defefe4f 100644 --- a/scripts/agent/telemetry-aggregator.mjs +++ b/scripts/agent/telemetry-aggregator.mjs @@ -61,9 +61,13 @@ function suggestOwner(stack) { function collectUnitTests(logFile = 'test_output.log') { const errors = []; - if (!fs.existsSync(logFile)) return errors; + // Check root and artifacts dir + const potentialPaths = [logFile, path.join(ARTIFACTS_DIR, logFile)]; + const filepath = potentialPaths.find(p => fs.existsSync(p)); - const content = fs.readFileSync(logFile, 'utf8'); + if (!filepath) return errors; + + const content = fs.readFileSync(filepath, 'utf8'); const lines = content.split('\n'); let currentError = null; let insideErrorBlock = false; @@ -182,7 +186,7 @@ function collectSmokeTests() { const content = JSON.parse(fs.readFileSync(path.join(ARTIFACTS_DIR, file), 'utf8')); // Expected format: { timestamp, status, error, logs } - if (content.status === 'failure') { + if (content.status && (content.status.toLowerCase() === 'failure' || content.status.toLowerCase() === 'fail')) { const errorMsg = content.error || 'Unknown Smoke Test Failure'; const sanitizedError = sanitize(errorMsg); @@ -258,6 +262,62 @@ function collectFuzzReports() { return errors; } +function collectLoadTests() { + const errors = []; + if (!fs.existsSync(ARTIFACTS_DIR)) return errors; + + const files = fs.readdirSync(ARTIFACTS_DIR).filter(f => f.startsWith('load-report-') && f.endsWith('.json')); + // Also check load-report.json + if (fs.existsSync(path.join(ARTIFACTS_DIR, 'load-report.json'))) { + files.push('load-report.json'); + } + + const uniqueFiles = [...new Set(files)]; + + for (const file of uniqueFiles) { + try { + const content = JSON.parse(fs.readFileSync(path.join(ARTIFACTS_DIR, file), 'utf8')); + + // Check for explicit errors + if (content.metrics && content.metrics.errors > 0) { + const errorBreakdown = content.metrics.error_breakdown ? JSON.stringify(content.metrics.error_breakdown, null, 2) : 'No breakdown'; + const sanitizedBreakdown = sanitize(errorBreakdown); + errors.push({ + source: `load-test:${file}`, + title: `Load Test Errors: ${content.metrics.errors} failures`, + details: [sanitizedBreakdown], + stack: `Load Test Errors:\n${sanitizedBreakdown}`, + fingerprint: getFingerprint(`load-test-errors-${sanitizedBreakdown}`), + severity: 'High', + owner: 'P2P Team' + }); + } + + // Check for bottlenecks + if (content.bottlenecks && content.bottlenecks.length > 0) { + for (let i = 0; i < content.bottlenecks.length; i++) { + const bn = content.bottlenecks[i]; + const rem = content.remediation ? content.remediation[i] : ''; + const sanitizedBn = sanitize(bn); + errors.push({ + source: `load-test:${file}`, + title: `Load Test Bottleneck: ${sanitizedBn}`, + details: [sanitize(rem)], + stack: `Bottleneck: ${sanitizedBn}\nRemediation: ${sanitize(rem)}`, + fingerprint: getFingerprint(`load-test-bottleneck-${sanitizedBn}`), + severity: 'Medium', + owner: 'P2P Team' + }); + } + } + + } catch (err) { + console.warn(`Failed to parse load report ${file}:`, err.message); + } + } + return errors; +} + function collectAgentLogs() { const errors = []; if (!fs.existsSync(ARTIFACTS_DIR)) return errors; @@ -419,6 +479,13 @@ async function main() { errors.push(...smokeTestErrors); } + // Load Tests + const loadTestErrors = collectLoadTests(); + if (loadTestErrors.length > 0) { + console.log(`Collected ${loadTestErrors.length} load test errors.`); + errors.push(...loadTestErrors); + } + // Fuzz Reports const fuzzErrors = collectFuzzReports(); if (fuzzErrors.length > 0) { diff --git a/scripts/run-unit-tests.mjs b/scripts/run-unit-tests.mjs index f22ad3ae..0a340dd7 100755 --- a/scripts/run-unit-tests.mjs +++ b/scripts/run-unit-tests.mjs @@ -1,5 +1,6 @@ import { spawn } from "node:child_process"; import { readdir, readFile } from "node:fs/promises"; +import fs from "node:fs"; import { fileURLToPath } from "node:url"; import path from "node:path"; @@ -101,14 +102,28 @@ async function run() { return; } + const logFile = path.join(rootDir, "artifacts", "test_unit.log"); + if (!fs.existsSync(path.dirname(logFile))) { + fs.mkdirSync(path.dirname(logFile), { recursive: true }); + } + const logStream = fs.createWriteStream(logFile); + for (const testFile of selectedTests) { const relativePath = path.relative(rootDir, testFile); console.log(`\n→ Running ${relativePath}`); + logStream.write(`\n→ Running ${relativePath}\n`); await new Promise((resolve, reject) => { const child = spawn(process.execPath, ["--import", setupImport.href, testFile], { - stdio: "inherit", + stdio: ["inherit", "pipe", "pipe"], }); + + child.stdout.pipe(process.stdout, { end: false }); + child.stdout.pipe(logStream, { end: false }); + + child.stderr.pipe(process.stderr, { end: false }); + child.stderr.pipe(logStream, { end: false }); + let finished = false; let timeoutId = null;