Bug 1704500: Add utility process test helpers r=gerard-majax

Also makes the existing utility process test functions a bit more general.

Depends on D162943

Differential Revision: https://phabricator.services.mozilla.com/D162944
This commit is contained in:
David Parks 2023-01-28 21:00:50 +00:00
Родитель 7e4c5e8fba
Коммит 1a3ddd6ba2
10 изменённых файлов: 177 добавлений и 73 удалений

Просмотреть файл

@ -137,6 +137,14 @@ class UtilityProcessManager final : public UtilityProcessHost::Listener {
return {};
}
Span<const UtilityActorName> GetActors(SandboxingKind aSbKind) {
auto proc = GetProcess(aSbKind);
if (!proc) {
return {};
}
return proc->mActors;
}
// Shutdown the Utility process for that sandbox.
void CleanShutdown(SandboxingKind aSandbox);

Просмотреть файл

@ -44,10 +44,10 @@ add_setup(async function setup() {
add_task(async function testKill() {
await runTest("small-shot.ogg", "Utility Generic", "vorbis audio decoder");
const audioDecoderPid = await findGenericAudioDecoder();
ok(audioDecoderPid > 0, `Valid PID found: ${audioDecoderPid}`);
await cleanUtilityProcessShutdown(audioDecoderPid, /* preferKill */ true);
await cleanUtilityProcessShutdown(
"audioDecoder_Generic",
true /* preferKill */
);
info("Waiting 15s to trigger mShutdownBlockers assertions");
await new Promise((resolve, reject) => {
@ -64,7 +64,7 @@ add_task(async function testShutdown() {
const audioDecoderPid = await findGenericAudioDecoder();
ok(audioDecoderPid > 0, `Valid PID found: ${audioDecoderPid}`);
await cleanUtilityProcessShutdown(audioDecoderPid);
await cleanUtilityProcessShutdown("audioDecoder_Generic");
info("Waiting 15s to trigger mShutdownBlockers assertions");
await new Promise((resolve, reject) => {

Просмотреть файл

@ -3,15 +3,16 @@
"use strict";
async function startAndCrashUtility(actors, actorsCheck) {
async function startAndCrashUtility(numUnknownActors, actorsCheck) {
const actors = Array(numUnknownActors).fill("unknown");
const utilityPid = await startUtilityProcess(actors);
await crashSomeUtility(utilityPid, actorsCheck);
}
// When running full suite, previous audio decoding tests might have left some
// running and this might interfere with our testing
// When running full suite, previous tests may have left some utility
// processes running and this might interfere with our testing.
add_setup(async function ensureNoExistingProcess() {
await killPendingUtilityProcess();
await killUtilityProcesses();
});
add_task(async function utilityNoActor() {

Просмотреть файл

@ -4,27 +4,10 @@
"use strict";
add_task(async () => {
const utilityPid = await startUtilityProcess();
await startUtilityProcess(["unknown"]);
SimpleTest.expectChildProcessCrash();
const utilityProcessGone = TestUtils.topicObserved("ipc:utility-shutdown");
info("Hard kill Utility Process");
const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
Ci.nsIProcessToolsService
);
ProcessTools.kill(utilityPid);
info(`Waiting for utility process ${utilityPid} to go away.`);
let [subject, data] = await utilityProcessGone;
ok(
subject instanceof Ci.nsIPropertyBag2,
"Subject needs to be a nsIPropertyBag2 to clean up properly"
);
is(
parseInt(data, 10),
utilityPid,
`Should match the crashed PID ${utilityPid} with ${data}`
);
await cleanUtilityProcessShutdown("unknown", true /* preferKill */);
});

Просмотреть файл

@ -6,7 +6,7 @@
// When running full suite, previous audio decoding tests might have left some
// running and this might interfere with our testing
add_setup(async function ensureNoExistingProcess() {
await utilityProcessTest().stopProcess();
await killUtilityProcesses();
});
add_task(async () => {
@ -72,5 +72,5 @@ add_task(async () => {
"Collected some explicit/ report"
);
await cleanUtilityProcessShutdown(utilityPid);
await cleanUtilityProcessShutdown();
});

Просмотреть файл

@ -10,10 +10,10 @@ Services.scriptloader.loadSubScript(
this
);
// When running full suite, previous audio decoding tests might have left some
// running and this might interfere with our testing
// When running full suite, previous tests may have left some utility
// processes running and this might interfere with our testing.
add_setup(async function ensureNoExistingProcess() {
await killPendingUtilityProcess();
await killUtilityProcesses();
});
add_task(async () => {
@ -72,5 +72,5 @@ add_task(async () => {
Services.profiler.StopProfiler();
await cleanUtilityProcessShutdown(utilityPid);
await cleanUtilityProcessShutdown();
});

Просмотреть файл

@ -4,6 +4,6 @@
"use strict";
add_task(async () => {
const pid = await startUtilityProcess();
await cleanUtilityProcessShutdown(pid);
await startUtilityProcess();
await cleanUtilityProcessShutdown();
});

Просмотреть файл

@ -12,14 +12,51 @@ const utilityProcessTest = () => {
const kGenericUtilitySandbox = 0;
const kGenericUtilityActor = "unknown";
async function startUtilityProcess(actors) {
// Start a generic utility process with the given array of utility actor names
// registered.
async function startUtilityProcess(actors = []) {
info("Start a UtilityProcess");
return utilityProcessTest().startProcess(actors);
}
async function cleanUtilityProcessShutdown(utilityPid, preferKill = false) {
info(`CleanShutdown Utility Process ${utilityPid}`);
ok(utilityPid !== undefined, "Utility needs to be defined");
// Returns an array of process infos for utility processes of the given type
// or all utility processes if actor is not defined.
async function getUtilityProcesses(actor = undefined) {
let procInfos = (await ChromeUtils.requestProcInfo()).children.filter(p => {
return (
p.type === "utility" &&
(actor == undefined ||
p.utilityActors.find(a => a.actorName.startsWith(actor)))
);
});
info(`Utility process infos = ${JSON.stringify(procInfos)}`);
return procInfos;
}
async function getUtilityPid(actor) {
let process = await getUtilityProcesses(actor);
is(process.length, 1, `exactly one ${actor} process exists`);
return process[0].pid;
}
async function checkUtilityExists(actor) {
info(`Looking for a running ${actor} utility process`);
const utilityPid = await getUtilityPid(actor);
ok(utilityPid > 0, `Found ${actor} utility process ${utilityPid}`);
return utilityPid;
}
// "Cleanly stop" a utility process. This will never leave a crash dump file.
// preferKill will "kill" the process (e.g. SIGABRT) instead of using the
// UtilityProcessManager.
// To "crash" -- i.e. shutdown and generate a crash dump -- use
// crashSomeUtility().
async function cleanUtilityProcessShutdown(actor, preferKill = false) {
info(`${preferKill ? "Kill" : "Clean shutdown"} Utility Process ${actor}`);
const utilityPid = await getUtilityPid(actor);
ok(utilityPid !== undefined, `Must have PID for ${actor} utility process`);
const utilityProcessGone = TestUtils.topicObserved(
"ipc:utility-shutdown",
@ -34,7 +71,8 @@ async function cleanUtilityProcessShutdown(utilityPid, preferKill = false) {
);
ProcessTools.kill(utilityPid);
} else {
await utilityProcessTest().stopProcess();
info(`Stopping Utility Process ${utilityPid}`);
await utilityProcessTest().stopProcess(actor);
}
let [subject, data] = await utilityProcessGone;
@ -51,22 +89,13 @@ async function cleanUtilityProcessShutdown(utilityPid, preferKill = false) {
ok(!subject.hasKey("dumpID"), "There should be no dumpID");
}
async function killPendingUtilityProcess() {
let audioDecoderProcesses = (
await ChromeUtils.requestProcInfo()
).children.filter(p => {
return (
p.type === "utility" &&
p.utilityActors.find(a => a.actorName.startsWith("audioDecoder_Generic"))
);
});
info(`audioDecoderProcesses=${JSON.stringify(audioDecoderProcesses)}`);
for (let audioDecoderProcess of audioDecoderProcesses) {
info(`Stopping audio decoder PID ${audioDecoderProcess.pid}`);
await cleanUtilityProcessShutdown(
audioDecoderProcess.pid,
/* preferKill */ true
);
async function killUtilityProcesses() {
let utilityProcesses = await getUtilityProcesses();
for (const utilityProcess of utilityProcesses) {
for (const actor of utilityProcess.utilityActors) {
info(`Stopping ${actor.actorName} utility process`);
await cleanUtilityProcessShutdown(actor.actorName, /* preferKill */ true);
}
}
}
@ -366,3 +395,22 @@ async function crashSomeUtility(utilityPid, actorsCheck) {
extrafile.remove(false);
}
}
// Crash a utility process and generate a crash dump. To close a utility
// process (forcefully or not) without a generating a crash, use
// cleanUtilityProcessShutdown.
async function crashSomeUtilityActor(
actor,
actorsCheck = () => {
return true;
}
) {
// Get PID for utility type
const procInfos = await getUtilityProcesses(actor);
ok(
procInfos.length == 1,
`exactly one ${actor} utility process should be found`
);
const utilityPid = procInfos[0].pid;
return crashSomeUtility(utilityPid, actorsCheck);
}

Просмотреть файл

@ -12,8 +12,51 @@
namespace mozilla::ipc {
static UtilityActorName UtilityActorNameFromString(
const nsACString& aStringName) {
using namespace mozilla::dom;
// We use WebIDLUtilityActorNames because UtilityActorNames is not designed
// for iteration.
for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) {
auto idlName = static_cast<UtilityActorName>(i);
const nsDependentCSubstring idlNameString(
WebIDLUtilityActorNameValues::GetString(idlName));
if (idlNameString.Equals(aStringName)) {
return idlName;
}
}
MOZ_CRASH("Unknown utility actor name");
}
// Find the utility process with the given actor or any utility process if
// the actor is UtilityActorName::EndGuard_.
static SandboxingKind FindUtilityProcessWithActor(UtilityActorName aActorName) {
RefPtr<UtilityProcessManager> utilityProc =
UtilityProcessManager::GetSingleton();
MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
for (size_t i = 0; i < SandboxingKind::COUNT; ++i) {
auto sbKind = static_cast<SandboxingKind>(i);
if (!utilityProc->Process(sbKind)) {
continue;
}
if (aActorName == UtilityActorName::EndGuard_) {
return sbKind;
}
for (auto actor : utilityProc->GetActors(sbKind)) {
if (actor == aActorName) {
return sbKind;
}
}
}
return SandboxingKind::COUNT;
}
NS_IMETHODIMP
UtilityProcessTest::StartProcess(int32_t aUnknownActors, JSContext* aCx,
UtilityProcessTest::StartProcess(const nsTArray<nsCString>& aActorsToRegister,
JSContext* aCx,
mozilla::dom::Promise** aOutPromise) {
NS_ENSURE_ARG(aOutPromise);
*aOutPromise = nullptr;
@ -32,20 +75,19 @@ UtilityProcessTest::StartProcess(int32_t aUnknownActors, JSContext* aCx,
UtilityProcessManager::GetSingleton();
MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
auto actors = aActorsToRegister.Clone();
utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY)
->Then(
GetCurrentSerialEventTarget(), __func__,
[promise, utilityProc, aUnknownActors]() {
[promise, utilityProc, actors = std::move(actors)] {
RefPtr<UtilityProcessParent> utilityParent =
utilityProc->GetProcessParent(SandboxingKind::GENERIC_UTILITY);
Maybe<int32_t> utilityPid =
utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
if (aUnknownActors > 0) {
RefPtr<UtilityProcessParent> utilityParent =
utilityProc->GetProcessParent(
SandboxingKind::GENERIC_UTILITY);
for (int32_t i = 0; i < aUnknownActors; i++) {
utilityProc->RegisterActor(utilityParent,
UtilityActorName::Unknown);
}
for (size_t i = 0; i < actors.Length(); ++i) {
auto uan = UtilityActorNameFromString(actors[i]);
utilityProc->RegisterActor(utilityParent, uan);
}
if (utilityPid.isSome()) {
promise->MaybeResolve(*utilityPid);
@ -64,14 +106,31 @@ UtilityProcessTest::StartProcess(int32_t aUnknownActors, JSContext* aCx,
}
NS_IMETHODIMP
UtilityProcessTest::StopProcess() {
UtilityProcessTest::StopProcess(const char* aActorName) {
using namespace mozilla::dom;
SandboxingKind sbKind;
if (aActorName) {
const nsDependentCString actorStringName(aActorName);
UtilityActorName actorName = UtilityActorNameFromString(actorStringName);
sbKind = FindUtilityProcessWithActor(actorName);
} else {
sbKind = FindUtilityProcessWithActor(UtilityActorName::EndGuard_);
}
if (sbKind == SandboxingKind::COUNT) {
MOZ_ASSERT_UNREACHABLE(
"Attempted to stop process for actor when no "
"such process exists");
return NS_ERROR_FAILURE;
}
RefPtr<UtilityProcessManager> utilityProc =
UtilityProcessManager::GetSingleton();
MOZ_ASSERT(utilityProc, "No UtilityprocessManager?");
utilityProc->CleanShutdown(SandboxingKind::GENERIC_UTILITY);
Maybe<int32_t> utilityPid =
utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY);
utilityProc->CleanShutdown(sbKind);
Maybe<int32_t> utilityPid = utilityProc->ProcessPid(sbKind);
MOZ_RELEASE_ASSERT(utilityPid.isNothing(),
"Should not have a utility process PID anymore");

Просмотреть файл

@ -12,16 +12,21 @@ interface nsIUtilityProcessTest : nsISupports
* ** Test-only Method **
*
* Allowing to start Utility Process from JS code.
*
* actorsToAdd: An array of actor names, taken from WebIDLUtilityActorName.
* Unlike normal utility processes, test processes launched this way do not
* have any associated actor names unless specified here. Empty by default.
*/
[implicit_jscontext]
Promise startProcess([optional] in int32_t unknownActors);
Promise startProcess([optional] in Array<ACString> actorsToAdd);
/**
* ** Test-only Method **
*
* Allowing to stop Utility Process from JS code.
* Default behavior is to stop any utility process.
*/
void stopProcess();
void stopProcess([optional] in string utilityActorName);
/**
* ** Test-only Method **