diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /ipc/glue/test | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/glue/test')
27 files changed, 1635 insertions, 0 deletions
diff --git a/ipc/glue/test/browser/browser.ini b/ipc/glue/test/browser/browser.ini new file mode 100644 index 0000000000..6bf835ab85 --- /dev/null +++ b/ipc/glue/test/browser/browser.ini @@ -0,0 +1,31 @@ +[DEFAULT] +support-files = head.js + +[browser_utility_audioDecodeCrash.js] +support-files = + ../../../../dom/media/test/small-shot.ogg + ../../../../dom/media/test/small-shot.mp3 + ../../../../dom/media/test/small-shot.m4a + ../../../../dom/media/test/small-shot.flac +skip-if = + !crashreporter + ccov +[browser_utility_crashReporter.js] +skip-if = + !crashreporter + ccov +[browser_utility_hard_kill.js] +[browser_utility_hard_kill_delayed.js] # bug 1754572: we really want hard_kill to be rust before hard_kill_delayed +[browser_utility_memoryReport.js] +skip-if = tsan # bug 1754554 +[browser_utility_multipleAudio.js] +support-files = + ../../../../dom/media/test/small-shot.ogg + ../../../../dom/media/test/small-shot.mp3 + ../../../../dom/media/test/small-shot.m4a + ../../../../dom/media/test/small-shot.flac +[browser_utility_profiler.js] +support-files = + ../../../../tools/profiler/tests/shared-head.js +skip-if = tsan # from tools/profiler/tests/browser/browser.ini, timing out on profiler tests? +[browser_utility_start_clean_shutdown.js] diff --git a/ipc/glue/test/browser/browser_audio_shutdown.ini b/ipc/glue/test/browser/browser_audio_shutdown.ini new file mode 100644 index 0000000000..a0ce9796f0 --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_shutdown.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = head.js + +[browser_utility_audio_shutdown.js] +support-files = + ../../../../dom/media/test/small-shot.ogg diff --git a/ipc/glue/test/browser/browser_child_hang.ini b/ipc/glue/test/browser/browser_child_hang.ini new file mode 100644 index 0000000000..94931561b0 --- /dev/null +++ b/ipc/glue/test/browser/browser_child_hang.ini @@ -0,0 +1,7 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ +[DEFAULT] +tags = ipc +environment = MOZ_TEST_CHILD_EXIT_HANG=3 + +[browser_child_hang.js] diff --git a/ipc/glue/test/browser/browser_child_hang.js b/ipc/glue/test/browser/browser_child_hang.js new file mode 100644 index 0000000000..925c9bb733 --- /dev/null +++ b/ipc/glue/test/browser/browser_child_hang.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// +// Try to open a tab. This provides code coverage for a few things, +// although currently there's no automated functional test of correctness: +// +// * On opt builds, when the tab is closed and the process exits, it +// will hang for 3s and the parent will kill it after 2s. +// +// * On debug[*] builds, the parent process will wait until the +// process exits normally; but also, on browser shutdown, the +// preallocated content processes will block parent shutdown in +// WillDestroyCurrentMessageLoop. +// +// [*] Also sanitizer and code coverage builds. +// + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "https://example.com/", + forceNewProcess: true, + }, + async function(browser) { + // browser.frameLoader.remoteTab.osPid is the child pid; once we + // have a way to get notifications about child process termination + // events, that could be useful. + ok(true, "Browser isn't broken"); + } + ); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 4000)); + ok(true, "Still running after child process (hopefully) exited"); +}); diff --git a/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js new file mode 100644 index 0000000000..864925e1da --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function getAudioDecoderPid(expectation) { + info("Finding a running AudioDecoder"); + + const actor = expectation.replace("Utility ", ""); + + let audioDecoderProcess = (await ChromeUtils.requestProcInfo()).children.find( + p => + p.type === "utility" && + p.utilityActors.find(a => a.actorName === `audioDecoder_${actor}`) + ); + ok( + audioDecoderProcess, + `Found the AudioDecoder ${actor} process at ${audioDecoderProcess.pid}` + ); + return audioDecoderProcess.pid; +} + +async function crashDecoder(expectation) { + const audioPid = await getAudioDecoderPid(expectation); + ok(audioPid > 0, `Found an audio decoder ${audioPid}`); + const actorIsAudioDecoder = actorNames => { + return actorNames.startsWith("audio-decoder-"); + }; + info(`Crashing audio decoder ${audioPid}`); + await crashSomeUtility(audioPid, actorIsAudioDecoder); +} + +async function runTest(src, withClose, expectation) { + info(`Add media tabs: ${src}`); + let tab = await addMediaTab(src); + + info("Play tab"); + await play(tab, expectation.process, expectation.decoder); + + info("Crash decoder"); + await crashDecoder(expectation.process); + + if (withClose) { + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); + + info("Create tab again"); + tab = await addMediaTab(src); + } + + info("Play tab again"); + await play(tab, expectation.process, expectation.decoder); + + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); +} + +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["media.utility-process.enabled", true]], + }); +}); + +async function testAudioCrash(withClose) { + info(`Running tests for audio decoder process crashing: ${withClose}`); + + SimpleTest.expectChildProcessCrash(); + + const platform = Services.appinfo.OS; + + for (let { src, expectations } of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + await runTest(src, withClose, expectations[platform]); + } +} + +add_task(async function testAudioCrashSimple() { + await testAudioCrash(false); +}); + +add_task(async function testAudioCrashClose() { + await testAudioCrash(true); +}); diff --git a/ipc/glue/test/browser/browser_utility_audio_shutdown.js b/ipc/glue/test/browser/browser_utility_audio_shutdown.js new file mode 100644 index 0000000000..ce46c18151 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_audio_shutdown.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// The purpose of that test is to reproduce edge case behaviors that one can +// have while running whole ipc/glue/test/browser/ suite but that could this +// way be intermittent and hard to diagnose. By having such a test we make sure +// it is cleanly reproduced and wont regress somewhat silently. + +"use strict"; + +async function runTest(src, process, decoder) { + info(`Add media tabs: ${src}`); + let tab = await addMediaTab(src); + + info("Play tab"); + await play(tab, process, decoder); + + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); +} + +async function findGenericAudioDecoder() { + const audioDecoders = (await ChromeUtils.requestProcInfo()).children.filter( + p => { + return ( + p.type === "utility" && + p.utilityActors.find(a => a.actorName === "audioDecoder_Generic") + ); + } + ); + ok(audioDecoders.length === 1, "Only one audio decoder present"); + return audioDecoders[0].pid; +} + +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["media.utility-process.enabled", true]], + }); +}); + +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); + + info("Waiting 15s to trigger mShutdownBlockers assertions"); + await new Promise((resolve, reject) => { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + setTimeout(resolve, 15 * 1000); + }); + + ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over"); +}); + +add_task(async function testShutdown() { + await runTest("small-shot.ogg", "Utility Generic", "vorbis audio decoder"); + + const audioDecoderPid = await findGenericAudioDecoder(); + ok(audioDecoderPid > 0, `Valid PID found: ${audioDecoderPid}`); + + await cleanUtilityProcessShutdown(audioDecoderPid); + + info("Waiting 15s to trigger mShutdownBlockers assertions"); + await new Promise((resolve, reject) => { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + setTimeout(resolve, 15 * 1000); + }); + + ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over"); +}); diff --git a/ipc/glue/test/browser/browser_utility_crashReporter.js b/ipc/glue/test/browser/browser_utility_crashReporter.js new file mode 100644 index 0000000000..7ed9d6c6a7 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_crashReporter.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function startAndCrashUtility(actors, actorsCheck) { + 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 +add_setup(async function ensureNoExistingProcess() { + await killPendingUtilityProcess(); +}); + +add_task(async function utilityNoActor() { + await startAndCrashUtility(0, actorNames => { + return actorNames === undefined; + }); +}); + +add_task(async function utilityOneActor() { + await startAndCrashUtility(1, actorNames => { + return actorNames === kGenericUtilityActor; + }); +}); + +add_task(async function utilityManyActors() { + await startAndCrashUtility(42, actorNames => { + return ( + actorNames === + Array(42) + .fill("unknown") + .join(", ") + ); + }); +}); diff --git a/ipc/glue/test/browser/browser_utility_hard_kill.js b/ipc/glue/test/browser/browser_utility_hard_kill.js new file mode 100644 index 0000000000..9849a5a419 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_hard_kill.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + const utilityPid = await startUtilityProcess(); + + 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}` + ); +}); diff --git a/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js new file mode 100644 index 0000000000..5ea8d0a75e --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + const utilityPid = await startUtilityProcess(); + + 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 + ); + + // Here we really want to exercise the fact that kill() might not be done + // right now but a bit later, and we should wait for the process to be dead + // before considering the test is finished. + // + // Without this, we get into bug 1754572 (where there was no setTimeout nor + // the wait) where the kill() operation ends up really killing the child a + // bit after the current test has been finished ; unfortunately, this happened + // right after the next test, browser_utility_memoryReport.js did start and + // even worse, after it thought it had started a new utility process. We were + // in fact re-using the one we started here, and when we wanted to query its + // pid in the browser_utility_memoryReport.js then the kill() happened, so + // no more process and the test intermittently failed. + // + // The timeout value of 50ms should be long enough to allow the test to finish + // and the next one to start and get a reference on the process we launched, + // and yet allow us to kill the process in the middle of the next test. Higher + // values would allow browser_utility_memoryReport.js to complete without + // reproducing the issue (both locally and on try). + // + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => { + ProcessTools.kill(utilityPid); + }, 50); + + 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}` + ); +}); diff --git a/ipc/glue/test/browser/browser_utility_memoryReport.js b/ipc/glue/test/browser/browser_utility_memoryReport.js new file mode 100644 index 0000000000..f6ce50d9ee --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_memoryReport.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// 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(); +}); + +add_task(async () => { + const utilityPid = await startUtilityProcess(); + + const gMgr = Cc["@mozilla.org/memory-reporter-manager;1"].getService( + Ci.nsIMemoryReporterManager + ); + ok(utilityPid !== undefined, `Utility process is running as ${utilityPid}`); + + var utilityReports = []; + + const performCollection = new Promise((resolve, reject) => { + // Record the reports from the live memory reporters then process them. + let handleReport = function( + aProcess, + aUnsafePath, + aKind, + aUnits, + aAmount, + aDescription + ) { + const expectedProcess = `Utility (pid: ${utilityPid}, sandboxingKind: ${kGenericUtilitySandbox})`; + if (aProcess !== expectedProcess) { + return; + } + + let report = { + process: aProcess, + path: aUnsafePath, + kind: aKind, + units: aUnits, + amount: aAmount, + description: aDescription, + }; + + utilityReports.push(report); + }; + + info("Memory report: Perform the call"); + gMgr.getReports(handleReport, null, resolve, null, false); + }); + + await performCollection; + + info( + `Collected ${utilityReports.length} reports from utility process ${utilityPid}` + ); + ok(!!utilityReports.length, "Collected some reports"); + ok( + utilityReports.filter(r => r.path === "vsize" && r.amount > 0).length === 1, + "Collected vsize report" + ); + ok( + utilityReports.filter(r => r.path === "resident" && r.amount > 0).length === + 1, + "Collected resident report" + ); + ok( + !!utilityReports.filter( + r => r.path.search(/^explicit\/.*/) >= 0 && r.amount > 0 + ).length, + "Collected some explicit/ report" + ); + + await cleanUtilityProcessShutdown(utilityPid); +}); diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio.js b/ipc/glue/test/browser/browser_utility_multipleAudio.js new file mode 100644 index 0000000000..d88ee014e2 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_multipleAudio.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function runTest(expectUtility) { + info( + `Running tests with decoding from Utility or RDD: expectUtility=${expectUtility}` + ); + + // Utility should now be the default, so dont toggle the pref unless we test + // RDD + if (!expectUtility) { + await SpecialPowers.pushPrefEnv({ + set: [["media.utility-process.enabled", expectUtility]], + }); + } + + const platform = Services.appinfo.OS; + + for (let { src, expectations } of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + const expectation = expectations[platform]; + + info(`Add media tabs: ${src}`); + let tabs = [await addMediaTab(src), await addMediaTab(src)]; + let playback = []; + + info("Play tabs"); + for (let tab of tabs) { + playback.push( + play( + tab, + expectUtility ? expectation.process : "RDD", + expectation.decoder + ) + ); + } + + info("Wait all playback"); + await Promise.all(playback); + + let allstop = []; + info("Stop tabs"); + for (let tab of tabs) { + allstop.push(stop(tab)); + } + + info("Wait all stop"); + await Promise.all(allstop); + + let remove = []; + info("Remove tabs"); + for (let tab of tabs) { + remove.push(BrowserTestUtils.removeTab(tab)); + } + + info("Wait all tabs to be removed"); + await Promise.all(remove); + } +} + +add_task(async function testAudioDecodingInUtility() { + await runTest(true); +}); + +add_task(async function testAudioDecodingInRDD() { + await runTest(false); +}); diff --git a/ipc/glue/test/browser/browser_utility_profiler.js b/ipc/glue/test/browser/browser_utility_profiler.js new file mode 100644 index 0000000000..14498d6d92 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_profiler.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from /tools/profiler/tests/shared-head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/tools/profiler/tests/browser/shared-head.js", + this +); + +// 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 killPendingUtilityProcess(); +}); + +add_task(async () => { + const utilityPid = await startUtilityProcess(); + + info("Start the profiler"); + startProfiler(); + + let profile; + await TestUtils.waitForCondition(async () => { + profile = await Services.profiler.getProfileDataAsync(); + return ( + // Search for process name to not be disturbed by other types of utility + // e.g. Utility AudioDecoder + profile.processes.filter( + ps => ps.threads[0].processName === "Utility Process" + ).length === 1 + ); + }, "Give time for the profiler to start and collect some samples"); + + info(`Check that the utility process ${utilityPid} is present.`); + let utilityProcessIndex = profile.processes.findIndex( + p => p.threads[0].pid == utilityPid + ); + Assert.notEqual(utilityProcessIndex, -1, "Could find index of utility"); + + Assert.equal( + profile.processes[utilityProcessIndex].threads[0].processType, + "utility", + "Profile has processType utility" + ); + + Assert.equal( + profile.processes[utilityProcessIndex].threads[0].name, + "GeckoMain", + "Profile has correct main thread name" + ); + + Assert.equal( + profile.processes[utilityProcessIndex].threads[0].processName, + "Utility Process", + "Profile has correct process name" + ); + + Assert.greater( + profile.processes[utilityProcessIndex].threads.length, + 0, + "The utility process should have threads" + ); + + Assert.equal( + profile.threads.length, + 1, + "The parent process should have only one thread" + ); + + Services.profiler.StopProfiler(); + + await cleanUtilityProcessShutdown(utilityPid); +}); diff --git a/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js new file mode 100644 index 0000000000..7820ca27e2 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + const pid = await startUtilityProcess(); + await cleanUtilityProcessShutdown(pid); +}); diff --git a/ipc/glue/test/browser/head.js b/ipc/glue/test/browser/head.js new file mode 100644 index 0000000000..1ea23796b4 --- /dev/null +++ b/ipc/glue/test/browser/head.js @@ -0,0 +1,368 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const utilityProcessTest = () => { + return Cc["@mozilla.org/utility-process-test;1"].createInstance( + Ci.nsIUtilityProcessTest + ); +}; + +const kGenericUtilitySandbox = 0; +const kGenericUtilityActor = "unknown"; + +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"); + + const utilityProcessGone = TestUtils.topicObserved( + "ipc:utility-shutdown", + (subject, data) => parseInt(data, 10) === utilityPid + ); + + if (preferKill) { + SimpleTest.expectChildProcessCrash(); + info(`Kill Utility Process ${utilityPid}`); + const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService( + Ci.nsIProcessToolsService + ); + ProcessTools.kill(utilityPid); + } else { + await utilityProcessTest().stopProcess(); + } + + 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}` + ); + + 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 + ); + } +} + +function audioTestData() { + return [ + { + src: "small-shot.ogg", + expectations: { + Android: { + process: "Utility Generic", + decoder: "vorbis audio decoder", + }, + Linux: { + process: "Utility Generic", + decoder: "vorbis audio decoder", + }, + WINNT: { + process: "Utility Generic", + decoder: "vorbis audio decoder", + }, + Darwin: { + process: "Utility Generic", + decoder: "vorbis audio decoder", + }, + }, + }, + { + src: "small-shot.mp3", + expectations: { + Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" }, + Linux: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + WINNT: { + process: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled") + ? "Utility Generic" + : "Utility WMF", + decoder: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled") + ? "ffvpx audio decoder" + : "wmf audio decoder", + }, + Darwin: { + process: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled") + ? "Utility Generic" + : "Utility AppleMedia", + decoder: SpecialPowers.getBoolPref("media.ffvpx.mp3.enabled") + ? "ffvpx audio decoder" + : "apple coremedia decoder", + }, + }, + }, + { + src: "small-shot.m4a", + expectations: { + // Add Android after Bug 1771196 + Linux: { + process: "Utility Generic", + decoder: "ffmpeg audio decoder", + }, + WINNT: { + process: "Utility WMF", + decoder: "wmf audio decoder", + }, + Darwin: { + process: "Utility AppleMedia", + decoder: "apple coremedia decoder", + }, + }, + }, + { + src: "small-shot.flac", + expectations: { + Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" }, + Linux: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + WINNT: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + Darwin: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + }, + }, + ]; +} + +async function addMediaTab(src) { + const tab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + forceNewProcess: true, + }); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + await SpecialPowers.spawn(browser, [src], createAudioElement); + return tab; +} + +async function play( + tab, + expectUtility, + expectDecoder, + expectContent = false, + expectJava = false +) { + let browser = tab.linkedBrowser; + return SpecialPowers.spawn( + browser, + [expectUtility, expectDecoder, expectContent, expectJava], + checkAudioDecoder + ); +} + +async function stop(tab) { + let browser = tab.linkedBrowser; + await SpecialPowers.spawn(browser, [], async function() { + let audio = content.document.querySelector("audio"); + audio.pause(); + }); +} + +async function createAudioElement(src) { + const doc = typeof content !== "undefined" ? content.document : document; + const ROOT = "https://example.com/browser/ipc/glue/test/browser"; + let audio = doc.createElement("audio"); + audio.setAttribute("controls", "true"); + audio.setAttribute("loop", true); + audio.src = `${ROOT}/${src}`; + doc.body.appendChild(audio); +} + +async function checkAudioDecoder( + expectedProcess, + expectedDecoder, + expectContent = false, + expectJava = false +) { + const doc = typeof content !== "undefined" ? content.document : document; + let audio = doc.querySelector("audio"); + const checkPromise = new Promise((resolve, reject) => { + const timeUpdateHandler = async ev => { + const debugInfo = await SpecialPowers.wrap(audio).mozRequestDebugInfo(); + const audioDecoderName = debugInfo.decoder.reader.audioDecoderName; + + const isExpectedDecoder = + audioDecoderName.indexOf(`${expectedDecoder}`) == 0; + ok( + isExpectedDecoder, + `playback ${audio.src} was from decoder '${audioDecoderName}', expected '${expectedDecoder}'` + ); + + const isExpectedProcess = + audioDecoderName.indexOf(`(${expectedProcess} remote)`) > 0; + const isJavaRemote = audioDecoderName.indexOf("(remote)") > 0; + const isOk = + (isExpectedProcess && !isJavaRemote && !expectContent && !expectJava) || // Running in Utility/RDD + (expectJava && !isExpectedProcess && isJavaRemote) || // Running in Java remote + (expectContent && !isExpectedProcess && !isJavaRemote); // Running in Content + + ok( + isOk, + `playback ${audio.src} was from process '${audioDecoderName}', expected '${expectedProcess}'` + ); + + if (isOk) { + resolve(); + } else { + reject(); + } + }; + + const startPlaybackHandler = async ev => { + ok( + await audio.play().then( + _ => true, + _ => false + ), + "audio started playing" + ); + + audio.addEventListener("timeupdate", timeUpdateHandler, { once: true }); + }; + + audio.addEventListener("canplaythrough", startPlaybackHandler, { + once: true, + }); + }); + + // We need to make sure the decoder is ready before play()ing otherwise we + // could get into bad situations + audio.load(); + return checkPromise; +} + +async function runMochitestUtilityAudio( + src, + { + expectUtility, + expectDecoder, + expectContent = false, + expectJava = false, + } = {} +) { + info(`Add media: ${src}`); + await createAudioElement(src); + let audio = document.querySelector("audio"); + ok(audio, "Found an audio element created"); + + info(`Play media: ${src}`); + await checkAudioDecoder( + expectUtility, + expectDecoder, + expectContent, + expectJava + ); + + info(`Pause media: ${src}`); + await audio.pause(); + + info(`Remove media: ${src}`); + document.body.removeChild(audio); +} + +async function crashSomeUtility(utilityPid, actorsCheck) { + SimpleTest.expectChildProcessCrash(); + + const crashMan = Services.crashmanager; + const utilityProcessGone = TestUtils.topicObserved( + "ipc:utility-shutdown", + (subject, data) => { + info(`ipc:utility-shutdown: data=${data} subject=${subject}`); + return parseInt(data, 10) === utilityPid; + } + ); + + info("prune any previous crashes"); + const future = new Date(Date.now() + 1000 * 60 * 60 * 24); + await crashMan.pruneOldCrashes(future); + + info("crash Utility Process"); + const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService( + Ci.nsIProcessToolsService + ); + + info(`Crash Utility Process ${utilityPid}`); + ProcessTools.crash(utilityPid); + + info(`Waiting for utility process ${utilityPid} to go away.`); + let [subject, data] = await utilityProcessGone; + ok( + parseInt(data, 10) === utilityPid, + `Should match the crashed PID ${utilityPid} with ${data}` + ); + ok( + subject instanceof Ci.nsIPropertyBag2, + "Subject needs to be a nsIPropertyBag2 to clean up properly" + ); + + const dumpID = subject.getPropertyAsAString("dumpID"); + ok(dumpID, "There should be a dumpID"); + + await crashMan.ensureCrashIsPresent(dumpID); + await crashMan.getCrashes().then(crashes => { + is(crashes.length, 1, "There should be only one record"); + const crash = crashes[0]; + ok( + crash.isOfType( + crashMan.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY], + crashMan.CRASH_TYPE_CRASH + ), + "Record should be a utility process crash" + ); + ok(crash.id === dumpID, "Record should have an ID"); + ok( + actorsCheck(crash.metadata.UtilityActorsName), + `Record should have the correct actors name for: ${crash.metadata.UtilityActorsName}` + ); + }); + + let minidumpDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile); + minidumpDirectory.append("minidumps"); + + let dumpfile = minidumpDirectory.clone(); + dumpfile.append(dumpID + ".dmp"); + if (dumpfile.exists()) { + info(`Removal of ${dumpfile.path}`); + dumpfile.remove(false); + } + + let extrafile = minidumpDirectory.clone(); + extrafile.append(dumpID + ".extra"); + info(`Removal of ${extrafile.path}`); + if (extrafile.exists()) { + extrafile.remove(false); + } +} diff --git a/ipc/glue/test/browser/mochitest_audio_off.ini b/ipc/glue/test/browser/mochitest_audio_off.ini new file mode 100644 index 0000000000..1e105c5f87 --- /dev/null +++ b/ipc/glue/test/browser/mochitest_audio_off.ini @@ -0,0 +1,12 @@ +[DEFAULT] +run-if = toolkit == 'android' && !isolated_process # Bug 1771452 +support-files = + head.js + ../../../../dom/media/test/small-shot.ogg + ../../../../dom/media/test/small-shot.mp3 + ../../../../dom/media/test/small-shot.m4a + ../../../../dom/media/test/small-shot.flac +prefs = + media.utility-process.enabled=false + +[test_utility_audio_off.html] diff --git a/ipc/glue/test/browser/mochitest_audio_on.ini b/ipc/glue/test/browser/mochitest_audio_on.ini new file mode 100644 index 0000000000..8d74572341 --- /dev/null +++ b/ipc/glue/test/browser/mochitest_audio_on.ini @@ -0,0 +1,12 @@ +[DEFAULT] +run-if = toolkit == 'android' && !isolated_process # Bug 1771452 +support-files = + head.js + ../../../../dom/media/test/small-shot.ogg + ../../../../dom/media/test/small-shot.mp3 + ../../../../dom/media/test/small-shot.m4a + ../../../../dom/media/test/small-shot.flac +prefs = + media.utility-process.enabled=true + +[test_utility_audio_on.html] diff --git a/ipc/glue/test/browser/moz.build b/ipc/glue/test/browser/moz.build new file mode 100644 index 0000000000..d0a85bebcd --- /dev/null +++ b/ipc/glue/test/browser/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +BROWSER_CHROME_MANIFESTS += [ + "browser.ini", + "browser_audio_shutdown.ini", + "browser_child_hang.ini", +] +MOCHITEST_MANIFESTS += ["mochitest_audio_off.ini", "mochitest_audio_on.ini"] diff --git a/ipc/glue/test/browser/test_utility_audio_off.html b/ipc/glue/test/browser/test_utility_audio_off.html new file mode 100644 index 0000000000..7df18ed383 --- /dev/null +++ b/ipc/glue/test/browser/test_utility_audio_off.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Audio decoder not in Utility process</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +(async function() { + const platform = SpecialPowers.Services.appinfo.OS; + for (let {src, expectations} of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + try { + await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: expectations[platform].decoder, expectContent: true, expectJava: false }); + } catch (ex) { + ok(false, "Failure"); + } + } + + for (let src of [ + "small-shot.m4a", + ]) { + try { + await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true }); + } catch (ex) { + ok(false, `Failure ${ex}`); + } + } + + SimpleTest.finish(); +})(); +</script> +</pre> +</body> +</html> diff --git a/ipc/glue/test/browser/test_utility_audio_on.html b/ipc/glue/test/browser/test_utility_audio_on.html new file mode 100644 index 0000000000..f473527520 --- /dev/null +++ b/ipc/glue/test/browser/test_utility_audio_on.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Audio decoder in Utility process</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +(async function() { + const platform = SpecialPowers.Services.appinfo.OS; + for (let {src, expectations} of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + try { + await runMochitestUtilityAudio(src, { expectUtility: expectations[platform].process, expectDecoder: expectations[platform].decoder, expectContent: false, expectJava: false }); + } catch (ex) { + ok(false, "Failure"); + } + } + + // Remove all after Bug 1771196 + for (let src of [ + "small-shot.m4a", + ]) { + try { + await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true }); + } catch (ex) { + ok(false, `Failure ${ex}`); + } + } + + SimpleTest.finish(); +})(); +</script> +</pre> +</body> +</html> diff --git a/ipc/glue/test/gtest/TestAsyncBlockers.cpp b/ipc/glue/test/gtest/TestAsyncBlockers.cpp new file mode 100644 index 0000000000..6f8d298621 --- /dev/null +++ b/ipc/glue/test/gtest/TestAsyncBlockers.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/ipc/AsyncBlockers.h" +#include "mozilla/gtest/MozHelpers.h" + +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsINamed.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +#define PROCESS_EVENTS_UNTIL(_done) \ + SpinEventLoopUntil("TestAsyncBlockers"_ns, [&]() { return _done; }); + +class TestAsyncBlockers : public ::testing::Test { + protected: + void SetUp() override { + SAVE_GDB_SLEEP(mOldSleepDuration); + return; + } + + void TearDown() final { RESTORE_GDB_SLEEP(mOldSleepDuration); } + + private: +#if defined(HAS_GDB_SLEEP_DURATION) + unsigned int mOldSleepDuration = 0; +#endif // defined(HAS_GDB_SLEEP_DURATION) +}; + +class Blocker {}; + +TEST_F(TestAsyncBlockers, Register) { + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + EXPECT_TRUE(true); +} + +TEST_F(TestAsyncBlockers, Register_Deregister) { + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + blockers.Deregister(blocker); + EXPECT_TRUE(true); +} + +TEST_F(TestAsyncBlockers, Register_WaitUntilClear) { + AsyncBlockers blockers; + bool done = false; + + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + + blockers.WaitUntilClear(5 * 1000)->Then(GetCurrentSerialEventTarget(), + __func__, [&]() { + EXPECT_TRUE(true); + done = true; + }); + + NS_ProcessPendingEvents(nullptr); + + blockers.Deregister(blocker); + + PROCESS_EVENTS_UNTIL(done); +} + +class AsyncBlockerTimerCallback : public nsITimerCallback, public nsINamed { + protected: + virtual ~AsyncBlockerTimerCallback(); + + public: + explicit AsyncBlockerTimerCallback() {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED +}; + +NS_IMPL_ISUPPORTS(AsyncBlockerTimerCallback, nsITimerCallback, nsINamed) + +AsyncBlockerTimerCallback::~AsyncBlockerTimerCallback() = default; + +NS_IMETHODIMP +AsyncBlockerTimerCallback::Notify(nsITimer* timer) { + // If we resolve through this, it means + // blockers.WaitUntilClear() started to wait for + // the completion of the timeout which is not + // good. + EXPECT_TRUE(false); + return NS_OK; +} + +NS_IMETHODIMP +AsyncBlockerTimerCallback::GetName(nsACString& aName) { + aName.AssignLiteral("AsyncBlockerTimerCallback"); + return NS_OK; +} + +TEST_F(TestAsyncBlockers, NoRegister_WaitUntilClear) { + AsyncBlockers blockers; + bool done = false; + + nsCOMPtr<nsITimer> timer = NS_NewTimer(); + ASSERT_TRUE(timer); + + RefPtr<AsyncBlockerTimerCallback> timerCb = new AsyncBlockerTimerCallback(); + timer->InitWithCallback(timerCb, 1 * 1000, nsITimer::TYPE_ONE_SHOT); + + blockers.WaitUntilClear(10 * 1000)->Then(GetCurrentSerialEventTarget(), + __func__, [&]() { + // If we resolve through this + // before the nsITimer it means we + // have been resolved before the 5s + // timeout + EXPECT_TRUE(true); + timer->Cancel(); + done = true; + }); + + PROCESS_EVENTS_UNTIL(done); +} + +TEST_F(TestAsyncBlockers, Register_WaitUntilClear_0s) { + AsyncBlockers blockers; + bool done = false; + + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + + blockers.WaitUntilClear(0)->Then(GetCurrentSerialEventTarget(), __func__, + [&]() { + EXPECT_TRUE(true); + done = true; + }); + + NS_ProcessPendingEvents(nullptr); + + blockers.Deregister(blocker); + + PROCESS_EVENTS_UNTIL(done); +} + +#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) && \ + !(defined(XP_DARWIN) && !defined(MOZ_DEBUG)) +static void DeregisterEmpty_Test() { + mozilla::gtest::DisableCrashReporter(); + + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Deregister(blocker); +} + +TEST_F(TestAsyncBlockers, DeregisterEmpty) { + ASSERT_DEATH_IF_SUPPORTED(DeregisterEmpty_Test(), ""); +} +#endif // defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) && + // !(defined(XP_DARWIN) && !defined(MOZ_DEBUG)) + +#undef PROCESS_EVENTS_UNTIL diff --git a/ipc/glue/test/gtest/TestUtilityProcess.cpp b/ipc/glue/test/gtest/TestUtilityProcess.cpp new file mode 100644 index 0000000000..c5d19c992f --- /dev/null +++ b/ipc/glue/test/gtest/TestUtilityProcess.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include "mozilla/ipc/UtilityProcessManager.h" + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) +# include "nsIAppShellService.h" +# include "nsServiceManagerUtils.h" +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + +#if defined(XP_WIN) +# include "mozilla/gtest/MozHelpers.h" +# include "mozilla/ipc/UtilityProcessImpl.h" +#endif // defined(XP_WIN) + +#ifdef MOZ_WIDGET_ANDROID +# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/android;1" +#endif // MOZ_WIDGET_ANDROID + +#ifdef XP_MACOSX +# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/mac;1" +#endif // XP_MACOSX + +using namespace mozilla; +using namespace mozilla::ipc; + +#define WAIT_FOR_EVENTS \ + SpinEventLoopUntil("UtilityProcess::emptyUtil"_ns, [&]() { return done; }); + +bool setupDone = false; + +class UtilityProcess : public ::testing::Test { + protected: + void SetUp() override { + if (setupDone) { + return; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + appShell = do_GetService(NS_APPSHELLSERVICE_CONTRACTID); +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mozilla::SandboxBroker::GeckoDependentInitialize(); +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + + setupDone = true; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + nsCOMPtr<nsIAppShellService> appShell; +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) +}; + +TEST_F(UtilityProcess, ProcessManager) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + ASSERT_NE(utilityProc, nullptr); +} + +TEST_F(UtilityProcess, NoProcess) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + EXPECT_NE(utilityProc, nullptr); + + Maybe<int32_t> noPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + ASSERT_TRUE(noPid.isNothing()); +} + +TEST_F(UtilityProcess, LaunchProcess) { + bool done = false; + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + EXPECT_NE(utilityProc, nullptr); + + int32_t thisPid = base::GetCurrentProcId(); + EXPECT_GE(thisPid, 1); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&]() mutable { + EXPECT_TRUE(true); + + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isSome()); + EXPECT_GE(*utilityPid, 1); + EXPECT_NE(*utilityPid, thisPid); + + printf_stderr("UtilityProcess running as %d\n", *utilityPid); + + done = true; + }, + [&](nsresult aError) mutable { + EXPECT_TRUE(false); + done = true; + }); + + WAIT_FOR_EVENTS; +} + +TEST_F(UtilityProcess, DestroyProcess) { + bool done = false; + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&]() { + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isSome()); + EXPECT_GE(*utilityPid, 1); + + utilityProc->CleanShutdown(SandboxingKind::GENERIC_UTILITY); + + utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isNothing()); + + EXPECT_TRUE(true); + done = true; + }, + [&](nsresult aError) { + EXPECT_TRUE(false); + done = true; + }); + + WAIT_FOR_EVENTS; +} + +#if defined(XP_WIN) +static void LoadLibraryCrash_Test() { + mozilla::gtest::DisableCrashReporter(); + // Just a uuidgen name to have something random + UtilityProcessImpl::LoadLibraryOrCrash( + L"2b49036e-6ba3-400c-a297-38fa1f6c5255.dll"); +} + +TEST_F(UtilityProcess, LoadLibraryCrash) { + ASSERT_DEATH_IF_SUPPORTED(LoadLibraryCrash_Test(), ""); +} +#endif // defined(XP_WIN) + +#undef WAIT_FOR_EVENTS diff --git a/ipc/glue/test/gtest/moz.build b/ipc/glue/test/gtest/moz.build new file mode 100644 index 0000000000..7b72cc7fe1 --- /dev/null +++ b/ipc/glue/test/gtest/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +Library("ipcgluetest") + +UNIFIED_SOURCES = [ + "TestAsyncBlockers.cpp", + "TestUtilityProcess.cpp", +] + +LOCAL_INCLUDES += [ + "/widget", + "/widget/android", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp new file mode 100644 index 0000000000..deeea4a4ab --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(ENABLE_TESTS) +# include "mozilla/ipc/UtilityProcessTest.h" +# include "mozilla/ipc/UtilityProcessManager.h" +# include "mozilla/dom/Promise.h" +# include "mozilla/ProcInfo.h" + +namespace mozilla::ipc { + +NS_IMETHODIMP +UtilityProcessTest::StartProcess(int32_t aUnknownActors, JSContext* aCx, + mozilla::dom::Promise** aOutPromise) { + NS_ENSURE_ARG(aOutPromise); + *aOutPromise = nullptr; + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr<dom::Promise> promise = dom::Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [promise, utilityProc, aUnknownActors]() { + 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); + } + } + if (utilityPid.isSome()) { + promise->MaybeResolve(*utilityPid); + } else { + promise->MaybeReject(NS_ERROR_NOT_AVAILABLE); + } + }, + [promise](nsresult aError) { + MOZ_ASSERT_UNREACHABLE( + "UtilityProcessTest; failure to get Utility process"); + promise->MaybeReject(aError); + }); + + promise.forget(aOutPromise); + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::StopProcess() { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + utilityProc->CleanShutdown(SandboxingKind::GENERIC_UTILITY); + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + MOZ_RELEASE_ASSERT(utilityPid.isNothing(), + "Should not have a utility process PID anymore"); + + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::TestTelemetryProbes() { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + for (RefPtr<UtilityProcessParent>& parent : + utilityProc->GetAllProcessesProcessParent()) { + Unused << parent->SendTestTelemetryProbes(); + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UtilityProcessTest, nsIUtilityProcessTest) + +} // namespace mozilla::ipc +#endif // defined(ENABLE_TESTS) diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h new file mode 100644 index 0000000000..6c80fce71b --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _include_ipc_glue_UtilityProcessTest_h_ +#define _include_ipc_glue_UtilityProcessTest_h_ + +#if defined(ENABLE_TESTS) +# include "nsServiceManagerUtils.h" +# include "nsIUtilityProcessTest.h" + +namespace mozilla::ipc { + +class UtilityProcessTest final : public nsIUtilityProcessTest { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUTILITYPROCESSTEST + + UtilityProcessTest() = default; + + private: + ~UtilityProcessTest() = default; +}; + +} // namespace mozilla::ipc +#endif // defined(ENABLE_TESTS) + +#endif // _include_ipc_glue_UtilityProcessTest_h_ diff --git a/ipc/glue/test/utility_process_xpcom/components.conf b/ipc/glue/test/utility_process_xpcom/components.conf new file mode 100644 index 0000000000..25208ba7fc --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/components.conf @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{0a4478f4-c5ae-4fb1-8686-d5b09fb99afb}', + 'contract_ids': ['@mozilla.org/utility-process-test;1'], + 'type': 'mozilla::ipc::UtilityProcessTest', + 'headers': ['mozilla/ipc/UtilityProcessTest.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] diff --git a/ipc/glue/test/utility_process_xpcom/moz.build b/ipc/glue/test/utility_process_xpcom/moz.build new file mode 100644 index 0000000000..f04b436cbe --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.ipc += ["UtilityProcessTest.h"] + +UNIFIED_SOURCES += ["UtilityProcessTest.cpp"] + +XPCOM_MANIFESTS += ["components.conf"] + +XPIDL_MODULE = "utility_process_xpcom_test" + +XPIDL_SOURCES += [ + "nsIUtilityProcessTest.idl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl new file mode 100644 index 0000000000..76bd1afade --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(0a4478f4-c5ae-4fb1-8686-d5b09fb99afb)] +interface nsIUtilityProcessTest : nsISupports +{ + /** + * ** Test-only Method ** + * + * Allowing to start Utility Process from JS code. + */ + [implicit_jscontext] + Promise startProcess([optional] in int32_t unknownActors); + + /** + * ** Test-only Method ** + * + * Allowing to stop Utility Process from JS code. + */ + void stopProcess(); + + /** + * ** Test-only Method ** + * + * Sending Telemetry probes + */ + void testTelemetryProbes(); +}; |