Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
let CrashReports;
// Local helper to create an empty file
function _create_file(dir, filename, mtime) {
const file = dir.clone();
file.append(filename);
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
file.lastModifiedTime = mtime;
return file;
}
/**
* Create a stub file for a crash that has already been submitted.
*
* @param {string} id — The UUID of the crash
* @param {Date} mtime — The time of the crash, encoded as the file mtime
*
* @return {nsIFile} Handle to the created file.
*/
function create_submitted_file(id, mtime = Date.now()) {
return _create_file(CrashReports.submittedDir, "bp-" + id + ".txt", mtime);
}
function _create_pending_base(id, mtime, ignored) {
const dir = CrashReports.pendingDir;
const dmp = _create_file(dir, id + ".dmp", mtime);
const extra = _create_file(dir, id + ".extra", mtime);
let ignore = null;
if (ignored) {
ignore = _create_file(dir, id + ".dmp.ignore", mtime);
}
return [dmp, extra, ignore];
}
/**
* Create stub files in the pending crash directory.
*
* @param {string} id — the UUID of the crash
* @param {Date} mtime — the time of the crash, encoded as the file mtime
*
* @return {[nsIFile, nsIFile, null]} Handles for the dmp and extra files
*/
function create_pending_files(id, mtime = Date.now()) {
return _create_pending_base(id, mtime, /*ignored*/ false);
}
/**
* Create stub files in the pending crash directory for a crash
* that was ignored by the user.
*
* @param {string} id — the UUID of the crash
* @param {Date} mtime — the time of the crash, encoded as the file mtime
*
* @return {[nsIFile, nsIFile, nsIFile]} Handles for dmp, extra and ignore files
*/
function create_ignored_files(id, mtime = Date.now()) {
return _create_pending_base(id, mtime, /*ignored*/ true);
}
/**
* Utility function to make sure the crash directories are
* empty. Typically used at the beginning of a test to ensure
* stable conditions.
*
*/
function clear_crash_dirs() {
for (let dir of [CrashReports.submittedDir, CrashReports.pendingDir]) {
if (dir.exists() && dir.isDirectory()) {
let entries = dir.directoryEntries;
while (entries.hasMoreElements()) {
let file = entries.nextFile;
file.remove(false);
}
}
}
}
/**
* Removes every file directly under the crash reports root (`reportsDir`)
* without descending into subdirectories. Used to clean InstallTime markers
* to ensure stable conditions.
*/
function clear_reports_dir() {
const dir = CrashReports.reportsDir;
if (!dir.exists() || !dir.isDirectory()) {
return;
}
let entries = dir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.nextFile;
if (entry.isFile()) {
entry.remove(false);
}
}
}
add_setup(async function () {
do_get_profile();
Services.prefs.setCharPref(
"breakpad.reportURL",
);
registerCleanupFunction(() => {
clear_reports_dir();
clear_crash_dirs();
Services.prefs.clearUserPref("breakpad.reportURL");
});
// We need to setup the fake dirs *before* importing the tested module so that
// its toplevel code points to it.
await makeFakeAppDir();
({ CrashReports } = ChromeUtils.importESModule(
"moz-src:///toolkit/crashreporter/CrashReports.sys.mjs"
));
});
// Trivial empty case
add_task(async function test_getReports_empty() {
clear_crash_dirs();
let reports = CrashReports.getReports();
Assert.equal(reports.length, 0);
});
// A couple new crashes.
add_task(async function test_getReports_onlypending() {
clear_crash_dirs();
let now = Date.now();
create_pending_files("00000001-1111-2222-3333-444444444444", now - 1000);
create_pending_files("00000002-1111-2222-3333-444444444444", now);
let reports = CrashReports.getReports();
Assert.equal(reports.length, 2);
Assert.ok(reports.every(r => r.pending));
Assert.ok(reports.every(r => !r.ignored));
Assert.deepEqual(
reports.map(r => r.id),
[
"00000002-1111-2222-3333-444444444444",
"00000001-1111-2222-3333-444444444444",
]
);
});
// A couple crashes that were already seen but not submitted.
add_task(async function test_getReports_onlyignored() {
clear_crash_dirs();
let now = Date.now();
create_ignored_files("00000001-1111-2222-3333-444444444444", now - 1000);
create_ignored_files("00000002-1111-2222-3333-444444444444", now);
let reports = CrashReports.getReports();
Assert.equal(reports.length, 2);
Assert.ok(reports.every(r => r.pending));
Assert.ok(reports.every(r => r.ignored));
Assert.deepEqual(
reports.map(r => r.id),
[
"00000002-1111-2222-3333-444444444444",
"00000001-1111-2222-3333-444444444444",
]
);
});
// A couple of crashes that were submitted.
add_task(async function test_getReports_onlysubmitted() {
clear_crash_dirs();
let now = Date.now();
create_submitted_file("00000001-1111-2222-3333-444444444444", now - 1000);
create_submitted_file("00000002-1111-2222-3333-444444444444", now);
let reports = CrashReports.getReports();
Assert.equal(reports.length, 2);
Assert.ok(reports.every(r => !r.pending));
Assert.ok(reports.every(r => !r.ignored));
Assert.deepEqual(
reports.map(r => [r.id, r.date]),
[
["bp-00000002-1111-2222-3333-444444444444", now],
["bp-00000001-1111-2222-3333-444444444444", now - 1000],
]
);
});
// A bunch of reports all mixed together
add_task(async function test_getReports_mixed() {
clear_crash_dirs();
let now = Date.now();
// Set time in different order to ensure sorting is done by date
create_submitted_file("00000001-1111-2222-3333-444444444444", now - 1000);
create_pending_files("00000002-1111-2222-3333-444444444444", now - 2000);
create_pending_files("00000003-1111-2222-3333-444444444444", now);
create_submitted_file("00000004-1111-2222-3333-444444444444", now - 500);
create_ignored_files("00000005-1111-2222-3333-444444444444", now - 1500);
create_pending_files("00000006-1111-2222-3333-444444444444", now - 3000);
create_ignored_files("00000007-1111-2222-3333-444444444444", now - 3500);
create_submitted_file("00000008-1111-2222-3333-444444444444", now - 2500);
let reports = CrashReports.getReports();
Assert.deepEqual(
reports.map(r => [r.id, r.date, r.pending, r.ignored]),
[
["00000003-1111-2222-3333-444444444444", now, true, false],
["bp-00000004-1111-2222-3333-444444444444", now - 500, false, false],
["bp-00000001-1111-2222-3333-444444444444", now - 1000, false, false],
["00000005-1111-2222-3333-444444444444", now - 1500, true, true],
["00000002-1111-2222-3333-444444444444", now - 2000, true, false],
["bp-00000008-1111-2222-3333-444444444444", now - 2500, false, false],
["00000006-1111-2222-3333-444444444444", now - 3000, true, false],
["00000007-1111-2222-3333-444444444444", now - 3500, true, true],
]
);
});
// No report if the report URL is not HTTP.
add_task(async function test_getReports_nohttp() {
clear_crash_dirs();
create_submitted_file("00000001-1111-2222-3333-444444444444");
create_pending_files("00000002-1111-2222-3333-444444444444");
create_ignored_files("00000003-1111-2222-3333-444444444444");
Services.prefs.setCharPref("breakpad.reportURL", "ftp://example.com/report");
try {
let reports = CrashReports.getReports();
Assert.equal(reports.length, 0);
} finally {
Services.prefs.setCharPref(
"breakpad.reportURL",
);
}
});
// Mock directory object allowing us to inject custom IFile-like objects,
// through the `files` argument representing the directory contents.
function fake_dir(files) {
return {
exists: () => true,
isDirectory: () => true,
get directoryEntries() {
let index = 0;
return {
hasMoreElements: () => index < files.length,
get nextFile() {
return files[index++];
},
};
},
};
}
// A fake file that only has `leafName` and `mtime` properties, the latter
// being either a value or a callable (to throw exceptions)
function fake_file(leafName, mtime) {
return {
leafName,
get lastModifiedTime() {
return typeof mtime === "function" ? mtime() : mtime;
},
};
}
// A report whose file vanished mid-scan is skipped without aborting the scan.
add_task(function test_getReports_deletedMidScan() {
clear_crash_dirs();
const now = Date.now();
const savedPendingDir = CrashReports.pendingDir;
CrashReports.pendingDir = fake_dir([
fake_file("00000001-1111-2222-3333-444444444444.dmp", now),
fake_file("00000002-1111-2222-3333-444444444444.dmp", () => {
throw Components.Exception("", Cr.NS_ERROR_FILE_NOT_FOUND);
}),
fake_file("00000003-1111-2222-3333-444444444444.dmp", now - 1000),
]);
try {
let reports = CrashReports.getReports();
Assert.deepEqual(
reports.map(r => r.id),
[
"00000001-1111-2222-3333-444444444444",
"00000003-1111-2222-3333-444444444444",
]
);
} finally {
CrashReports.pendingDir = savedPendingDir;
}
});
// Errors other than NS_ERROR_FILE_NOT_FOUND are not swallowed.
add_task(function test_getReports_otherErrorPropagates() {
clear_crash_dirs();
const savedPendingDir = CrashReports.pendingDir;
CrashReports.pendingDir = fake_dir([
fake_file("00000001-1111-2222-3333-444444444444.dmp", () => {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}),
]);
try {
Assert.throws(() => CrashReports.getReports(), /NS_ERROR_FAILURE/);
} finally {
CrashReports.pendingDir = savedPendingDir;
}
});
// `pruneInstallTimeFiles` only deletes `InstallTime*` files older than the
// supplied threshold and ignores everything else in the directory.
add_task(async function test_pruneInstallTimeFiles() {
clear_crash_dirs();
clear_reports_dir();
const now = Date.now();
const old = now - 365 * 24 * 60 * 60 * 1000; // 1 year ago
const recent = now - 10 * 60 * 1000; // 10 minutes ago
const oldInstall = _create_file(
CrashReports.reportsDir,
"InstallTime20200101000000",
old
);
const recentInstall = _create_file(
CrashReports.reportsDir,
"InstallTime20240101000000",
recent
);
const unrelated = _create_file(CrashReports.reportsDir, "foo.txt", old);
await CrashReports.pruneInstallTimeFiles(30 * 24 * 60 * 60 * 1000); // 30 days
Assert.ok(!oldInstall.exists(), "Old InstallTime file was removed");
Assert.ok(recentInstall.exists(), "Recent InstallTime file was preserved");
Assert.ok(unrelated.exists(), "Non-InstallTime file was preserved");
});
// `pruneInstallTimeFiles` is a no-op when the crash reports directory does
// not exist (fresh profile case).
add_task(async function test_pruneInstallTimeFiles_missingDir() {
const savedReportsDir = CrashReports.reportsDir;
const missingDir = savedReportsDir.clone();
missingDir.append("nonexistent-subdir");
CrashReports.reportsDir = missingDir;
try {
await CrashReports.pruneInstallTimeFiles(30 * 24 * 60 * 60 * 1000);
} finally {
CrashReports.reportsDir = savedReportsDir;
}
});
// `deletePendingReport` removes the `.dmp`, `.extra`, `.dmp.ignore`, and
// `.memory.json.gz` files belonging to a single crash, leaving siblings
// belonging to other crashes alone.
add_task(async function test_deletePendingReport() {
clear_crash_dirs();
const targetId = "00000001-1111-2222-3333-444444444444";
const otherId = "00000002-1111-2222-3333-444444444444";
const [targetDmp, targetExtra, targetIgnore] = _create_pending_base(
targetId,
Date.now(),
/*ignored*/ true
);
const targetMemory = _create_file(
CrashReports.pendingDir,
targetId + ".memory.json.gz",
Date.now()
);
const [otherDmp, otherExtra] = create_pending_files(otherId);
await CrashReports.deletePendingReport(targetId);
Assert.ok(!targetDmp.exists(), ".dmp was removed");
Assert.ok(!targetExtra.exists(), ".extra was removed");
Assert.ok(!targetIgnore.exists(), ".dmp.ignore was removed");
Assert.ok(!targetMemory.exists(), ".memory.json.gz was removed");
Assert.ok(otherDmp.exists(), "untargeted .dmp is preserved");
Assert.ok(otherExtra.exists(), "untargeted .extra is preserved");
});
// `deletePendingReport` tolerates the optional companion files being absent.
add_task(async function test_deletePendingReport_partial() {
clear_crash_dirs();
const id = "00000001-1111-2222-3333-444444444444";
const [dmp, extra] = create_pending_files(id);
await CrashReports.deletePendingReport(id);
Assert.ok(!dmp.exists());
Assert.ok(!extra.exists());
});
// `deleteSubmittedReport` removes only the targeted `bp-<id>.txt`.
add_task(async function test_deleteSubmittedReport() {
clear_crash_dirs();
const a = create_submitted_file("00000001-1111-2222-3333-444444444444");
const b = create_submitted_file("00000002-1111-2222-3333-444444444444");
await CrashReports.deleteSubmittedReport(
"bp-00000001-1111-2222-3333-444444444444"
);
Assert.ok(!a.exists(), "targeted bp-*.txt was removed");
Assert.ok(b.exists(), "untargeted bp-*.txt was preserved");
});
// `deleteSubmittedReport` tolerates the file already being absent.
add_task(async function test_deleteSubmittedReport_missing() {
clear_crash_dirs();
await CrashReports.deleteSubmittedReport(
"bp-ffffffff-ffff-ffff-ffff-ffffffffffff"
);
});