commit 2d763ae17a22d3c3ced19e000ae2219015c47900
parent 56b805b2dc4f18718173577886959ca75ddf160b
Author: Florian Quèze <florian@queze.net>
Date: Tue, 2 Dec 2025 06:54:05 +0000
Bug 2003359 - include failure message and crash signatures in the xpcshell-date.json files, r=jmaher.
Differential Revision: https://phabricator.services.mozilla.com/D274637
Diffstat:
3 files changed, 74 insertions(+), 12 deletions(-)
diff --git a/testing/timings/JSON_FORMAT.md b/testing/timings/JSON_FORMAT.md
@@ -85,9 +85,10 @@ String tables for efficient storage. All strings are deduplicated and stored onc
"XPPf5b1DRJrcBndDp9o74x.1", // Retry 1
...
],
- "messages": [ // SKIP status messages (only for tests that were skipped)
+ "messages": [ // Test messages (for SKIP and FAIL statuses)
"skip-if: os == 'linux'",
"disabled due to bug 123456",
+ "Expected 5, got 10", // Failure message
...
],
"crashSignatures": [ // Crash signatures (only for crashed tests)
@@ -162,9 +163,16 @@ Each `testRuns[testId][statusId]` contains data for all runs of that test with t
"taskIdIds": [45, 67, ...],
"durations": [0, 0, ...],
"timestamps": [100, 200, ...],
- "messageIds": [5, 5, ...] // Only present for SKIP status - indices into tables.messages (null if no message)
+ "messageIds": [5, 5, ...] // Present for SKIP and FAIL statuses - indices into tables.messages (null if no message)
},
- // statusId 3 (e.g., "CRASH")
+ // statusId 3 (e.g., "FAIL-PARALLEL")
+ {
+ "taskIdIds": [78, ...],
+ "durations": [1234, ...],
+ "timestamps": [250, ...],
+ "messageIds": [12, ...] // Present for SKIP and FAIL statuses - indices into tables.messages (null if no message)
+ },
+ // statusId 4 (e.g., "CRASH")
{
"taskIdIds": [89, ...],
"durations": [5678, ...],
diff --git a/testing/timings/fetch-xpcshell-data.js b/testing/timings/fetch-xpcshell-data.js
@@ -271,6 +271,11 @@ async function processJobsWithWorkers(jobs, targetDate = null) {
});
}
+// Helper function to determine if a status should include message data
+function shouldIncludeMessage(status) {
+ return status === "SKIP" || status.startsWith("FAIL");
+}
+
// Create string tables and store raw data efficiently
function createDataTables(jobResults) {
const tables = {
@@ -387,8 +392,8 @@ function createDataTables(jobResults) {
durations: [],
timestamps: [],
};
- // Only include messageIds array for SKIP status
- if (timing.status === "SKIP") {
+ // Include messageIds array for statuses that should have messages
+ if (shouldIncludeMessage(timing.status)) {
statusGroup.messageIds = [];
}
// Only include crash data arrays for CRASH status
@@ -404,8 +409,8 @@ function createDataTables(jobResults) {
statusGroup.durations.push(Math.round(timing.duration));
statusGroup.timestamps.push(timing.timestamp);
- // Store message ID for SKIP status (or null if no message)
- if (timing.status === "SKIP") {
+ // Store message ID for statuses that should include messages (or null if no message)
+ if (shouldIncludeMessage(timing.status)) {
const messageId = timing.message
? findStringIndex("messages", timing.message)
: null;
@@ -582,7 +587,7 @@ function sortStringTablesByFrequency(dataStructure) {
timestamps: statusGroup.timestamps,
};
- // Remap message IDs for SKIP status
+ // Remap message IDs for status groups that have messages
if (statusGroup.messageIds) {
remapped.messageIds = statusGroup.messageIds.map(oldId =>
oldId === null ? null : indexMaps.messages.get(oldId)
@@ -852,7 +857,7 @@ async function processJobsAndCreateData(
if (statusGroup.minidumps) {
run.minidump = statusGroup.minidumps[i];
}
- // Include message data if this is a SKIP status group
+ // Include message data if this status group has messages
if (statusGroup.messageIds) {
run.messageId = statusGroup.messageIds[i];
}
diff --git a/testing/timings/profile-worker.js b/testing/timings/profile-worker.js
@@ -7,6 +7,13 @@ const fs = require("fs");
const path = require("path");
const zlib = require("zlib");
+// Normalize failure messages to remove task-specific and time-specific information
+function normalizeMessage(message) {
+ return message
+ ?.replace(/task_\d+/g, "task_id")
+ .replace(/\nRejection date: [^\n]+/g, "");
+}
+
// Extract parallel execution time ranges from markers
function extractParallelRanges(markers) {
const parallelRanges = [];
@@ -150,6 +157,34 @@ function extractTestTimings(profile) {
});
}
+ // Extract TestStatus markers (FAIL, ERROR) for failure messages
+ const failStringId = stringArray.indexOf("FAIL");
+ const errorStringId = stringArray.indexOf("ERROR");
+ const testStatusMarkers = [];
+
+ for (let i = 0; i < markers.length; i++) {
+ const nameId = markers.name[i];
+ if (nameId !== failStringId && nameId !== errorStringId) {
+ continue;
+ }
+ const data = markers.data[i];
+ if (!data || data.type !== "TestStatus" || !data.test) {
+ continue;
+ }
+
+ testStatusMarkers.push({
+ test: data.test,
+ nameId,
+ time: markers.startTime[i],
+ message: normalizeMessage(data.message),
+ });
+ }
+
+ // Sort TestStatus markers by test and then time for efficient lookup
+ testStatusMarkers.sort(
+ (a, b) => a.test.localeCompare(b.test) || a.time - b.time
+ );
+
const testStringId = stringArray.indexOf("test");
const timings = [];
@@ -170,10 +205,13 @@ function extractTestTimings(profile) {
// Handle both structured and plain text logs
if (data.type === "Test") {
// Structured log format
- testPath = data.test || data.name;
+ const fullTestId = data.test || data.name;
+ testPath = fullTestId;
status = data.status || "UNKNOWN";
- // Normalize line breaks in message (convert \r\n to \n)
- message = data.message ? data.message.replace(/\r\n/g, "\n") : null;
+ // Normalize line breaks in message (convert \r\n to \n) and apply normalizations
+ message = normalizeMessage(
+ data.message ? data.message.replace(/\r\n/g, "\n") : null
+ );
// Check if this is an expected failure (FAIL status but green color)
if (status === "FAIL" && data.color === "green") {
@@ -194,6 +232,17 @@ function extractTestTimings(profile) {
}
// Keep other statuses as-is
+ // For failure statuses, look up the message from TestStatus markers
+ if (status.startsWith("FAIL")) {
+ const testStartTime = markers.startTime[i];
+ const statusMarker = testStatusMarkers.find(
+ m => m.test === fullTestId && m.time >= testStartTime
+ );
+ if (statusMarker && statusMarker.message) {
+ message = statusMarker.message;
+ }
+ }
+
// Extract the actual test file path from the test field
// Format: "xpcshell-parent-process.toml:dom/indexedDB/test/unit/test_fileListUpgrade.js"
if (testPath && testPath.includes(":")) {