Bug 1652156 - Add RDD Process Testing to the Sandbox Testing Framework. r=handyman,mattwoodrow

Differential Revision: https://phabricator.services.mozilla.com/D116894
This commit is contained in:
Alexandre Lissy 2021-06-24 06:51:24 +00:00
parent 6cacc3a511
commit 426e10f0e2
10 changed files with 173 additions and 59 deletions

View File

@ -11,6 +11,10 @@ include protocol PProfiler;
include protocol PRemoteDecoderManager;
include protocol PVideoBridge;
#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
include protocol PSandboxTesting;
#endif
using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h";
[MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h";
[MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h";
@ -51,6 +55,10 @@ parent:
async GetUntrustedModulesData() returns (UntrustedModulesData? data);
#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
#endif
child:
async InitCrashReporter(NativeThreadId threadId);

View File

@ -50,6 +50,10 @@
#include "nsDebugImpl.h"
#include "nsThreadManager.h"
#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
# include "mozilla/SandboxTestingChild.h"
#endif
namespace mozilla {
using namespace ipc;
@ -253,6 +257,17 @@ mozilla::ipc::IPCResult RDDParent::RecvPreferenceUpdate(const Pref& aPref) {
return IPC_OK();
}
#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
mozilla::ipc::IPCResult RDDParent::RecvInitSandboxTesting(
Endpoint<PSandboxTestingChild>&& aEndpoint) {
if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) {
return IPC_FAIL(
this, "InitSandboxTesting failed to initialise the child process.");
}
return IPC_OK();
}
#endif
void RDDParent::ActorDestroy(ActorDestroyReason aWhy) {
if (AbnormalShutdown == aWhy) {
NS_WARNING("Shutting down RDD process early due to a crash!");

View File

@ -49,6 +49,10 @@ class RDDParent final : public PRDDParent {
mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref);
mozilla::ipc::IPCResult RecvUpdateVar(const GfxVarUpdate& pref);
#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
mozilla::ipc::IPCResult RecvInitSandboxTesting(
Endpoint<PSandboxTestingChild>&& aEndpoint);
#endif
void ActorDestroy(ActorDestroyReason aWhy) override;
private:

View File

@ -7,11 +7,14 @@
IPDL_SOURCES += [
"PMediaDecoderParams.ipdlh",
"PRDD.ipdl",
"PRemoteDecoder.ipdl",
"PRemoteDecoderManager.ipdl",
]
PREPROCESSED_IPDL_SOURCES += [
"PRDD.ipdl",
]
EXPORTS.mozilla += [
"RDDChild.h",
"RDDParent.h",

View File

@ -14,6 +14,8 @@
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/GPUChild.h"
#include "mozilla/net/SocketProcessParent.h"
#include "mozilla/RDDProcessManager.h"
#include "mozilla/RDDChild.h"
#include "mozilla/ipc/Endpoint.h"
#include "nsIOService.h"
@ -25,6 +27,18 @@ namespace mozilla {
NS_IMPL_ISUPPORTS(SandboxTest, mozISandboxTest)
inline void UnsetEnvVariable(const nsCString& aEnvVarName) {
nsCString aEnvVarNameFull = aEnvVarName + "="_ns;
int rv_unset =
#ifdef XP_UNIX
unsetenv(aEnvVarName.get());
#endif // XP_UNIX
#ifdef XP_WIN
_putenv(aEnvVarNameFull.get());
#endif // XP_WIN
MOZ_ASSERT(rv_unset == 0, "Error unsetting env var");
}
GeckoProcessType GeckoProcessStringToType(const nsCString& aString) {
for (GeckoProcessType type = GeckoProcessType(0);
type < GeckoProcessType::GeckoProcessType_End;
@ -61,62 +75,77 @@ SandboxTest::StartTests(const nsTArray<nsCString>& aProcessesList) {
return NS_ERROR_ILLEGAL_VALUE;
}
RefPtr<ProcessPromise::Private> mProcessPromise =
MakeRefPtr<ProcessPromise::Private>(__func__);
switch (type) {
case GeckoProcessType_Content: {
nsTArray<ContentParent*> parents;
ContentParent::GetAll(parents);
MOZ_ASSERT(parents.Length() > 0);
mSandboxTestingParents[type] =
InitializeSandboxTestingActors(parents[0]);
if (parents[0]) {
mProcessPromise->Resolve(
std::move(InitializeSandboxTestingActors(parents[0])), __func__);
} else {
mProcessPromise->Reject(NS_ERROR_FAILURE, __func__);
MOZ_ASSERT_UNREACHABLE("SandboxTest; failure to get Content process");
}
break;
}
case GeckoProcessType_GPU: {
gfx::GPUProcessManager* gpuProc = gfx::GPUProcessManager::Get();
gfx::GPUChild* gpuChild = gpuProc ? gpuProc->GetGPUChild() : nullptr;
if (!gpuChild) {
// There is no GPU process for this OS. Report test done.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
MOZ_RELEASE_ASSERT(observerService);
observerService->NotifyObservers(nullptr, "sandbox-test-done", 0);
return NS_OK;
if (gpuChild) {
mProcessPromise->Resolve(
std::move(InitializeSandboxTestingActors(gpuChild)), __func__);
} else {
mProcessPromise->Reject(NS_OK, __func__);
}
break;
}
mSandboxTestingParents[type] = InitializeSandboxTestingActors(gpuChild);
case GeckoProcessType_RDD: {
RDDProcessManager* rddProc = RDDProcessManager::Get();
rddProc->LaunchRDDProcess()->Then(
GetMainThreadSerialEventTarget(), __func__,
[mProcessPromise, rddProc]() {
RDDChild* rddChild = rddProc ? rddProc->GetRDDChild() : nullptr;
if (rddChild) {
return mProcessPromise->Resolve(
std::move(InitializeSandboxTestingActors(rddChild)),
__func__);
}
return mProcessPromise->Reject(NS_ERROR_FAILURE, __func__);
},
[mProcessPromise](nsresult aError) {
MOZ_ASSERT_UNREACHABLE("SandboxTest; failure to get RDD process");
return mProcessPromise->Reject(aError, __func__);
});
break;
}
case GeckoProcessType_Socket: {
// mochitest harness force this variable, but we actually do not want
// that
int rv_unset =
#ifdef XP_UNIX
unsetenv("MOZ_DISABLE_SOCKET_PROCESS");
#endif // XP_UNIX
#ifdef XP_WIN
_putenv("MOZ_DISABLE_SOCKET_PROCESS=");
#endif // XP_WIN
MOZ_ASSERT(rv_unset == 0, "Error unsetting env var");
UnsetEnvVariable("MOZ_DISABLE_SOCKET_PROCESS"_ns);
nsresult rv_pref =
Preferences::SetBool("network.process.enabled", true);
MOZ_ASSERT(rv_pref == NS_OK, "Error enforcing pref");
MOZ_ASSERT(net::gIOService, "No gIOService?");
RefPtr<SandboxTest> self = this;
net::gIOService->CallOrWaitForSocketProcess([self, type]() {
net::gIOService->CallOrWaitForSocketProcess([mProcessPromise]() {
// If socket process was previously disabled by env,
// nsIOService code will take some time before it creates the new
// process and it triggers this callback
//
// If that happens, then by the time we reach the end of StartTests()
// the mSandboxTestingParents[type] might not yet have been updated
// this is why below we allow for a delayed dispatch to give a chance
net::SocketProcessParent* parent =
net::SocketProcessParent::GetSingleton();
self->mSandboxTestingParents[type] =
InitializeSandboxTestingActors(parent);
if (parent) {
return mProcessPromise->Resolve(
std::move(InitializeSandboxTestingActors(parent)), __func__);
}
return mProcessPromise->Reject(NS_ERROR_FAILURE, __func__);
});
break;
}
@ -127,26 +156,27 @@ SandboxTest::StartTests(const nsTArray<nsCString>& aProcessesList) {
return NS_ERROR_ILLEGAL_VALUE;
}
if (!mSandboxTestingParents[type]) {
if (type == GeckoProcessType_Socket) {
// Give a chance to the socket process to be present and to the callback
// above to be ran. We delay by 5 seconds, but hopefully since it is all
// on the main thread, the value itself should not be important.
RefPtr<SandboxTest> self = this;
NS_DelayedDispatchToCurrentThread(
NS_NewRunnableFunction(
"SandboxTest::StartTests",
[self, type]() {
if (!self->mSandboxTestingParents[type]) {
MOZ_ASSERT_UNREACHABLE(
"SandboxTest failed to get a Parent");
}
}),
5e3 /* delay by five seconds */);
} else {
return NS_ERROR_FAILURE;
}
}
RefPtr<SandboxTest> self = this;
RefPtr<ProcessPromise> aPromise(mProcessPromise);
aPromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[self, type](SandboxTestingParent* aValue) {
self->mSandboxTestingParents[type] = std::move(aValue);
return NS_OK;
},
[](nsresult aError) {
if (aError == NS_OK) {
// There is no such process for this OS. Report test done.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
MOZ_RELEASE_ASSERT(observerService);
observerService->NotifyObservers(nullptr, "sandbox-test-done",
nullptr);
return NS_OK;
}
MOZ_ASSERT_UNREACHABLE("SandboxTest; failure to get a process");
return NS_ERROR_FAILURE;
});
}
return NS_OK;
}

View File

@ -9,12 +9,14 @@
#include "SandboxTestingParent.h"
#include "mozISandboxTest.h"
#include "mozilla/GfxMessageUtils.h"
#include "mozilla/MozPromise.h"
#if !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
# error "This file should not be used outside of debug with tests"
#endif
namespace mozilla {
class SandboxTest : public mozISandboxTest {
public:
NS_DECL_ISUPPORTS
@ -22,6 +24,11 @@ class SandboxTest : public mozISandboxTest {
SandboxTest() : mSandboxTestingParents{nullptr} {};
// We allow nsresult to be rejected with values:
// - NS_ERROR_FAILURE in obvious case of error
// - NS_OK in case of success to complete the code but missing process (GPU)
using ProcessPromise = MozPromise<SandboxTestingParent*, nsresult, true>;
private:
virtual ~SandboxTest() = default;
static constexpr size_t NumProcessTypes =

View File

@ -66,6 +66,10 @@ void SandboxTestingChild::Bind(Endpoint<PSandboxTestingChild>&& aEndpoint) {
RunTestsContent(this);
}
if (XRE_IsRDDProcess()) {
RunTestsRDD(this);
}
if (XRE_IsSocketProcess()) {
RunTestsSocket(this);
}
@ -96,6 +100,12 @@ bool SandboxTestingChild::RecvShutDown() {
return true;
}
void SandboxTestingChild::ReportNoTests() {
SendReportTestResults("dummy_test"_ns, /* shouldSucceed */ true,
/* didSucceed */ true,
"The test framework fails if there are no cases."_ns);
}
#ifdef XP_UNIX
template <typename F>
void SandboxTestingChild::ErrnoTest(const nsCString& aName, bool aExpectSuccess,
@ -104,6 +114,14 @@ void SandboxTestingChild::ErrnoTest(const nsCString& aName, bool aExpectSuccess,
PosixTest(aName, aExpectSuccess, status);
}
template <typename F>
void SandboxTestingChild::ErrnoValueTest(const nsCString& aName,
bool aExpectEquals, int aExpectedErrno,
F&& aFunction) {
int status = aFunction() >= 0 ? 0 : errno;
PosixTest(aName, aExpectEquals, status == aExpectedErrno);
}
void SandboxTestingChild::PosixTest(const nsCString& aName, bool aExpectSuccess,
int aStatus) {
bool succeeded = aStatus == 0;

View File

@ -40,6 +40,11 @@ class SandboxTestingChild : public PSandboxTestingChild {
virtual bool RecvShutDown();
// Helper to return that no test have been executed. Tests should make sure
// they have some fallback through that otherwise the framework will consider
// absence of test report as a failure.
inline void ReportNoTests();
#ifdef XP_UNIX
// For test cases that return an error number or 0, like newer POSIX APIs.
void PosixTest(const nsCString& aName, bool aExpectSuccess, int aStatus);
@ -49,11 +54,18 @@ class SandboxTestingChild : public PSandboxTestingChild {
// is used only in this function call (so `[&]` captures are safe).
template <typename F>
void ErrnoTest(const nsCString& aName, bool aExpectSuccess, F&& aFunction);
// Similar to ErrnoTest, except that we want to compare a specific `errno`
// being returned (or not).
template <typename F>
void ErrnoValueTest(const nsCString& aName, bool aExpectEquals,
int aExpectedErrno, F&& aFunction);
#endif
private:
explicit SandboxTestingChild(SandboxTestingThread* aThread,
Endpoint<PSandboxTestingChild>&& aEndpoint);
void Bind(Endpoint<PSandboxTestingChild>&& aEndpoint);
UniquePtr<SandboxTestingThread> mThread;

View File

@ -13,6 +13,10 @@
# include <netdb.h>
# ifdef XP_LINUX
# include <sys/prctl.h>
# include <sys/ioctl.h>
# include <termios.h>
# include <sys/resource.h>
# include <sys/time.h>
# endif // XP_LINUX
# include <sys/socket.h>
# include <sys/stat.h>
@ -50,11 +54,7 @@ void RunTestsContent(SandboxTestingChild* child) {
[&] { return clock_getres(CLOCK_REALTIME, &res); });
#else // XP_UNIX
child->SendReportTestResults(
"dummy_test"_ns,
/* shouldSucceed */ true,
/* didSucceed */ true,
"The test framework fails if there are no cases."_ns);
child->ReportNoTests();
#endif // XP_UNIX
}
@ -84,12 +84,29 @@ void RunTestsSocket(SandboxTestingChild* child) {
# endif // XP_LINUX
#else // XP_UNIX
child->SendReportTestResults(
"dummy_test"_ns,
/* shouldSucceed */ true,
/* didSucceed */ true,
"The test framework fails if there are no cases."_ns);
child->ReportNoTests();
#endif // XP_UNIX
}
void RunTestsRDD(SandboxTestingChild* child) {
MOZ_ASSERT(child, "No SandboxTestingChild*?");
#ifdef XP_UNIX
# ifdef XP_LINUX
child->ErrnoValueTest("ioctl_tiocsti"_ns, false, ENOSYS, [&] {
int rv = ioctl(1, TIOCSTI, "x");
return rv;
});
struct rusage res;
child->ErrnoTest("getrusage"_ns, true, [&] {
int rv = getrusage(RUSAGE_SELF, &res);
return rv;
});
# endif // XP_LINUX
#else // XP_UNIX
child->ReportNoTests();
#endif
}
} // namespace mozilla

View File

@ -14,7 +14,7 @@ function test() {
// GPU process might not run depending on the platform, so we need it to be
// the last one of the list to allow the remainingTests logic below to work
// as expected.
var processTypes = ["tab", "socket", "gpu"];
var processTypes = ["tab", "socket", "rdd", "gpu"];
// A callback called after each test-result.
let sandboxTestResult = (subject, topic, data) => {