tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit b20332d9188e3380d327f4f226b564a3367f4029
parent 2d763ae17a22d3c3ced19e000ae2219015c47900
Author: Florian Quèze <florian@queze.net>
Date:   Tue,  2 Dec 2025 06:54:05 +0000

Bug 2003359 - include the bugzilla components of each test in the xpcshell-date.json files, r=jmaher.

Differential Revision: https://phabricator.services.mozilla.com/D274638

Diffstat:
Mtesting/timings/JSON_FORMAT.md | 15+++++++++++++--
Mtesting/timings/fetch-xpcshell-data.js | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 93 insertions(+), 2 deletions(-)

diff --git a/testing/timings/JSON_FORMAT.md b/testing/timings/JSON_FORMAT.md @@ -95,6 +95,12 @@ String tables for efficient storage. All strings are deduplicated and stored onc "mozilla::dom::Something::Crash", "EMPTY: no crashing thread identified", ... + ], + "components": [ // Bugzilla components (Product :: Component format) + "Core :: Storage: IndexedDB", + "Testing :: XPCShell Harness", + "Firefox :: General", + ... ] } ``` @@ -120,12 +126,13 @@ const jobName = tables.jobNames[taskInfo.jobNameIds[taskIdId]]; // "te ### testInfo -Maps test IDs to their test paths and names. These are parallel arrays indexed by `testId`: +Maps test IDs to their test paths, names, and components. These are parallel arrays indexed by `testId`: ```json { "testPathIds": [0, 0, 1, 2, ...], // Index into tables.testPaths - "testNameIds": [0, 1, 2, 3, ...] // Index into tables.testNames + "testNameIds": [0, 1, 2, 3, ...], // Index into tables.testNames + "componentIds": [5, 5, 12, null, ...] // Index into tables.components (null if unknown) } ``` @@ -135,6 +142,8 @@ const testId = 10; const testPath = tables.testPaths[testInfo.testPathIds[testId]]; // "dom/indexedDB/test/unit" const testName = tables.testNames[testInfo.testNameIds[testId]]; // "test_foo.js" const fullPath = testPath ? `${testPath}/${testName}` : testName; +const componentId = testInfo.componentIds[testId]; +const component = componentId !== null ? tables.components[componentId] : "Unknown"; // "Core :: Storage: IndexedDB" ``` ### testRuns @@ -385,4 +394,6 @@ Dates are sorted in descending order (newest first). - **Task ID formats differ between files:** - Test timing data: Always includes retry suffix (e.g., `"YJJe4a0CRIqbAmcCo8n63w.0"`) - Resource usage data: Omits `.0` for retry 0 (e.g., `"YJJe4a0CRIqbAmcCo8n63w"`), includes suffix for retries > 0 (e.g., `"YJJe4a0CRIqbAmcCo8n63w.1"`) +- **Component mapping:** Components are fetched from the TaskCluster index `gecko.v2.mozilla-central.latest.source.source-bugzilla-info` and mapped to test paths. The component ID in `testInfo.componentIds` may be `null` if the test path is not found in the mapping +- Components are formatted as `"Product :: Component"` (e.g., `"Core :: Storage: IndexedDB"`) - The data structure is optimized for sequential access patterns used by the dashboards diff --git a/testing/timings/fetch-xpcshell-data.js b/testing/timings/fetch-xpcshell-data.js @@ -29,6 +29,7 @@ const PROFILE_CACHE_DIR = "./profile-cache"; let previousRunData = null; let allJobsCache = null; +let componentsData = null; if (!fs.existsSync(OUTPUT_DIR)) { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); @@ -271,6 +272,62 @@ async function processJobsWithWorkers(jobs, targetDate = null) { }); } +// Fetch Bugzilla component mapping data +async function fetchComponentsData() { + if (componentsData) { + return componentsData; + } + + console.log("Fetching Bugzilla component mapping..."); + const url = `${TASKCLUSTER_BASE_URL}/api/index/v1/task/gecko.v2.mozilla-central.latest.source.source-bugzilla-info/artifacts/public/components-normalized.json`; + + try { + componentsData = await fetchJson(url); + console.log("Component mapping loaded successfully"); + return componentsData; + } catch (error) { + console.error("Failed to fetch component mapping:", error); + return null; + } +} + +// Look up component for a test path +function findComponentForPath(testPath) { + if (!componentsData || !componentsData.paths) { + return null; + } + + const parts = testPath.split("/"); + let current = componentsData.paths; + + for (const part of parts) { + if (typeof current === "number") { + return current; + } + if (typeof current === "object" && current !== null && part in current) { + current = current[part]; + } else { + return null; + } + } + + return typeof current === "number" ? current : null; +} + +// Get component string from component ID +function getComponentString(componentId) { + if (!componentsData || !componentsData.components || componentId == null) { + return null; + } + + const component = componentsData.components[String(componentId)]; + if (!component || !Array.isArray(component) || component.length !== 2) { + return null; + } + + return `${component[0]} :: ${component[1]}`; +} + // Helper function to determine if a status should include message data function shouldIncludeMessage(status) { return status === "SKIP" || status.startsWith("FAIL"); @@ -287,6 +344,7 @@ function createDataTables(jobResults) { taskIds: [], messages: [], crashSignatures: [], + components: [], }; // Maps for O(1) string lookups @@ -299,6 +357,7 @@ function createDataTables(jobResults) { taskIds: new Map(), messages: new Map(), crashSignatures: new Map(), + components: new Map(), }; // Task info maps task ID index to repository and job name indexes @@ -311,6 +370,7 @@ function createDataTables(jobResults) { const testInfo = { testPathIds: [], testNameIds: [], + componentIds: [], }; // Map for fast testId lookup: fullPath -> testId @@ -363,9 +423,17 @@ function createDataTables(jobResults) { const testPathId = findStringIndex("testPaths", testPath); const testNameId = findStringIndex("testNames", testName); + // Look up the component for this test + const componentIdRaw = findComponentForPath(fullPath); + const componentString = getComponentString(componentIdRaw); + const componentId = componentString + ? findStringIndex("components", componentString) + : null; + testId = testInfo.testPathIds.length; testInfo.testPathIds.push(testPathId); testInfo.testNameIds.push(testNameId); + testInfo.componentIds.push(componentId); testIdMap.set(fullPath, testId); } @@ -450,6 +518,7 @@ function sortStringTablesByFrequency(dataStructure) { taskIds: new Array(tables.taskIds.length).fill(0), messages: new Array(tables.messages.length).fill(0), crashSignatures: new Array(tables.crashSignatures.length).fill(0), + components: new Array(tables.components.length).fill(0), }; // Count taskInfo references @@ -471,6 +540,11 @@ function sortStringTablesByFrequency(dataStructure) { for (const testNameId of testInfo.testNameIds) { frequencyCounts.testNames[testNameId]++; } + for (const componentId of testInfo.componentIds) { + if (componentId !== null) { + frequencyCounts.components[componentId]++; + } + } // Count testRuns references for (const testGroup of testRuns) { @@ -566,6 +640,9 @@ function sortStringTablesByFrequency(dataStructure) { testNameIds: testInfo.testNameIds.map(oldId => indexMaps.testNames.get(oldId) ), + componentIds: testInfo.componentIds.map(oldId => + oldId === null ? null : indexMaps.components.get(oldId) + ), }; // Remap testRuns indices @@ -1141,6 +1218,9 @@ async function main() { await fetchPreviousRunData(); } + // Fetch component mapping data + await fetchComponentsData(); + // Check for --revision parameter (format: project:revision) const revisionIndex = process.argv.findIndex(arg => arg === "--revision"); if (revisionIndex !== -1 && revisionIndex + 1 < process.argv.length) {