Bug 1566855 - Submit incomplete crashes by synthesizing a minimal .extra file with sensible crash annotations r=mconley

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

--HG--
rename : toolkit/crashreporter/test/browser/browser_aboutCrashesResubmit.js => toolkit/crashreporter/test/browser/browser_aboutCrashesIncomplete.js
extra : moz-landing-system : lando
This commit is contained in:
Gabriele Svelto 2019-08-20 20:54:35 +00:00
parent 6faa60ff7c
commit 303c5ce8d0
5 changed files with 248 additions and 29 deletions

View File

@ -9,6 +9,9 @@ const { FileUtils } = ChromeUtils.import(
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const {
parseKeyValuePairs,
parseKeyValuePairsFromFileAsync,
@ -125,6 +128,37 @@ function getPendingMinidump(id) {
});
}
async function synthesizeExtraFile(extra) {
let data =
"ServerURL=https://crash-reports.mozilla.com/submit?id=" +
Services.appinfo.ID +
"&version=" +
Services.appinfo.version +
"&buildid=" +
Services.appinfo.appBuildID +
"\n" +
"Vendor=" +
Services.appinfo.vendor +
"\n" +
"ProductName=" +
Services.appinfo.name +
"\n" +
"ProductID=" +
Services.appinfo.ID +
"\n" +
"Version=" +
Services.appinfo.version +
"\n" +
"BuildID=" +
Services.appinfo.appBuildID +
"\n" +
"ReleaseChannel=" +
AppConstants.MOZ_UPDATE_CHANNEL +
"\n";
await OS.File.writeAtomic(extra, data, { encoding: "utf-8" });
}
async function writeSubmittedReportAsync(crashID, viewURL) {
let strings = await getL10nStrings();
let data = strings.crashid.replace("%s", crashID);
@ -343,12 +377,16 @@ Submitter.prototype = {
OS.File.exists(memory),
]);
if (!dumpExists || !extraExists) {
if (!dumpExists) {
this.notifyStatus(FAILED);
this.cleanup();
return this.submitStatusPromise;
}
if (!extraExists) {
await synthesizeExtraFile(extra);
}
this.dump = dump;
this.extra = extra;
this.memory = memoryExists ? memory : null;

View File

@ -3,6 +3,7 @@ support-files =
head.js
[browser_aboutCrashes.js]
[browser_aboutCrashesIncomplete.js]
[browser_aboutCrashesResubmit.js]
[browser_bug471404.js]
[browser_clearReports.js]

View File

@ -13,6 +13,18 @@ add_task(async function test() {
info("about:crashes loaded");
return ContentTask.spawn(browser, crashes, crashes => {
const doc = content.document;
const submitted = doc.getElementById("reportListSubmitted");
Assert.ok(
!submitted.classList.contains("hidden"),
"the submitted crash list is visible"
);
const unsubmitted = doc.getElementById("reportListUnsubmitted");
Assert.ok(
unsubmitted.classList.contains("hidden"),
"the unsubmitted crash list is hidden"
);
const crashIds = doc.getElementsByClassName("crash-id");
Assert.equal(
crashIds.length,
@ -30,5 +42,41 @@ add_task(async function test() {
}
);
clear_fake_crashes(crD, crashes);
const pendingCrash = addPendingCrashreport(crD, Date.now(), { foo: "bar" });
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:crashes" },
browser => {
info("about:crashes loaded");
return ContentTask.spawn(browser, pendingCrash, pendingCrash => {
const doc = content.document;
const submitted = doc.getElementById("reportListSubmitted");
Assert.ok(
submitted.classList.contains("hidden"),
"the submitted crash list is hidden"
);
const unsubmitted = doc.getElementById("reportListUnsubmitted");
Assert.ok(
!unsubmitted.classList.contains("hidden"),
"the unsubmitted crash list is visible"
);
const crashIds = doc.getElementsByClassName("crash-id");
Assert.equal(
crashIds.length,
1,
"about:crashes lists correct number of crash reports"
);
const pendingRow = doc.getElementById(pendingCrash.id);
Assert.equal(
pendingRow.cells[0].textContent,
pendingCrash.id,
"about:crashes lists pending crash IDs correctly"
);
});
}
);
cleanup_fake_appdir();
});

View File

@ -0,0 +1,102 @@
const SERVER_URL =
"http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
var oldServerUrl = null;
function set_fake_crashreporter_url() {
let env = Cc["@mozilla.org/process/environment;1"].getService(
Ci.nsIEnvironment
);
oldServerUrl = env.get("MOZ_CRASHREPORTER_URL");
env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
}
function reset_fake_crashreporter_url() {
let env = Cc["@mozilla.org/process/environment;1"].getService(
Ci.nsIEnvironment
);
env.set("MOZ_CRASHREPORTER_URL", oldServerUrl);
}
function cleanup_and_finish() {
try {
cleanup_fake_appdir();
} catch (ex) {}
Services.prefs.clearUserPref("breakpad.reportURL");
BrowserTestUtils.removeTab(gBrowser.selectedTab);
reset_fake_crashreporter_url();
finish();
}
/*
* check_submit_incomplete
*
* Check that crash reports that are missing the .extra file can be submitted.
* This will check if the submission process works by clicking on the "Submit"
* button in the about:crashes page and then verifies that the submission
* received on the server side contains the minimum set of crash annotations
* required by a valid report.
*/
function check_submit_incomplete(tab, crash) {
let crashReportPromise = TestUtils.topicObserved(
"crash-report-status",
(subject, data) => {
return data == "success";
}
);
crashReportPromise.then(result => {
let crashData = convertPropertyBag(result[0]);
ok(crashData.serverCrashID, "Should have a serverCrashID set.");
ok(crashData.extra.ProductName, "Should have a ProductName field.");
ok(crashData.extra.ReleaseChannel, "Should have a ReleaseChannel field.");
ok(crashData.extra.Vendor, "Should have a Vendor field.");
ok(crashData.extra.Version, "Should have a Version field.");
ok(crashData.extra.BuildID, "Should have a BuildID field.");
ok(crashData.extra.ProductID, "Should have a ProductID field.");
cleanup_and_finish();
});
const browser = gBrowser.getBrowserForTab(tab);
function csp_onsuccess() {
browser.removeEventListener("CrashSubmitSucceeded", csp_onsuccess, true);
ok(true, "the crash report was submitted successfully");
}
function csp_fail() {
browser.removeEventListener("CrashSubmitFailed", csp_fail, true);
ok(false, "failed to submit crash report!");
}
browser.addEventListener("CrashSubmitSucceeded", csp_onsuccess, true);
browser.addEventListener("CrashSubmitFailed", csp_fail, true);
ContentTask.spawn(browser, crash.id, id => {
const submitButton = content.document
.getElementById(id)
.getElementsByClassName("submit-button")[0];
submitButton.click();
});
}
function convertPropertyBag(aBag) {
let result = {};
for (let { name, value } of aBag.enumerator) {
if (value instanceof Ci.nsIPropertyBag) {
value = convertPropertyBag(value);
}
result[name] = value;
}
return result;
}
function test() {
waitForExplicitFinish();
set_fake_crashreporter_url();
const appD = make_fake_appdir();
const crD = appD.clone();
crD.append("Crash Reports");
let crash = addIncompletePendingCrashreport(crD, Date.now());
BrowserTestUtils.openNewForegroundTab(gBrowser, "about:crashes").then(tab => {
check_submit_incomplete(tab, crash);
});
}

View File

@ -10,6 +10,15 @@ function create_subdir(dir, subdirname) {
return subdir;
}
function generate_uuid() {
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
Ci.nsIUUIDGenerator
);
let uuid = uuidGenerator.generateUUID().toString();
// ditch the {}
return uuid.substring(1, uuid.length - 1);
}
// need to hold on to this to unregister for cleanup
var _provider = null;
@ -70,18 +79,13 @@ function cleanup_fake_appdir() {
function add_fake_crashes(crD, count) {
let results = [];
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
Ci.nsIUUIDGenerator
);
let submitdir = crD.clone();
submitdir.append("submitted");
// create them from oldest to newest, to ensure that about:crashes
// displays them in the correct order
let date = Date.now() - count * 60000;
for (let i = 0; i < count; i++) {
let uuid = uuidGenerator.generateUUID().toString();
// ditch the {}
uuid = "bp-" + uuid.substring(1, uuid.length - 2);
let uuid = "bp-" + generate_uuid();
let fn = uuid + ".txt";
let file = submitdir.clone();
file.append(fn);
@ -97,6 +101,17 @@ function add_fake_crashes(crD, count) {
return results;
}
function clear_fake_crashes(crD, crashes) {
let submitdir = crD.clone();
submitdir.append("submitted");
for (let i of crashes) {
let fn = i.id + ".txt";
let file = submitdir.clone();
file.append(fn);
file.remove(false);
}
}
function writeDataToFile(file, data) {
var fstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
Ci.nsIFileOutputStream
@ -112,30 +127,45 @@ function writeDataToFile(file, data) {
fstream.close();
}
function writeCrashReportFile(dir, uuid, suffix, date, data) {
let file = dir.clone();
file.append(uuid + suffix);
writeDataToFile(file, data);
file.lastModifiedTime = date;
}
function writeMinidumpFile(dir, uuid, date) {
// that's the start of a valid minidump, anyway
writeCrashReportFile(dir, uuid, ".dmp", date, "MDMP");
}
function writeExtraFile(dir, uuid, date, data) {
let extradata = "";
for (let x in data) {
extradata += x + "=" + data[x] + "\n";
}
writeCrashReportFile(dir, uuid, ".extra", date, extradata);
}
function writeMemoryReport(dir, uuid, date) {
let data = "Let's pretend this is a memory report";
writeCrashReportFile(dir, uuid, ".memory.json.gz", date, data);
}
function addPendingCrashreport(crD, date, extra) {
let pendingdir = crD.clone();
pendingdir.append("pending");
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
Ci.nsIUUIDGenerator
);
let uuid = uuidGenerator.generateUUID().toString();
// ditch the {}
uuid = uuid.substring(1, uuid.length - 1);
let dumpfile = pendingdir.clone();
dumpfile.append(uuid + ".dmp");
writeDataToFile(dumpfile, "MDMP"); // that's the start of a valid minidump, anyway
let extrafile = pendingdir.clone();
extrafile.append(uuid + ".extra");
let extradata = "";
for (let x in extra) {
extradata += x + "=" + extra[x] + "\n";
}
writeDataToFile(extrafile, extradata);
let memoryfile = pendingdir.clone();
memoryfile.append(uuid + ".memory.json.gz");
writeDataToFile(memoryfile, "Let's pretend this is a memory report");
dumpfile.lastModifiedTime = date;
extrafile.lastModifiedTime = date;
memoryfile.lastModifiedTime = date;
let uuid = generate_uuid();
writeMinidumpFile(pendingdir, uuid, date);
writeExtraFile(pendingdir, uuid, date, extra);
writeMemoryReport(pendingdir, uuid, date);
return { id: uuid, date, pending: true, extra };
}
function addIncompletePendingCrashreport(crD, date) {
let pendingdir = crD.clone();
pendingdir.append("pending");
let uuid = generate_uuid();
writeMinidumpFile(pendingdir, uuid, date);
return { id: uuid, date, pending: true };
}