Bug 1825259 - Implement ScopedTestResultReporter to help print assertion failures in death test child processes. r=gbrown

Gtest checks (with ASSERT_* or EXPECT_* macros) in gtest death test child
processes are usually ignored with no way of manually enabling them.

This patch implements a helper class, ScopedTestResultReporter, that when it is
the current test reporter will print TestPartResults to stderr. Stderr of a
death test child process will get printed by the parent only if the testcase
failed. A death test child can only signal to the parent to fail the testcase
by means of the exit code (or the absence of exit). To help signal an
unexpected exit code, ScopedTestResultReporter can be made to exit the process
automatically as it goes out of scope, using different exit codes depending on
the death test child's test status where 0=pass, 1=nonfatal failure, and
2=fatal failure.

Note that when used like this, the death test parent process must be set up to
expect an exit code of 0 from the death test child process, typically through
`EXPECT_EXIT(DoTestThatExits(), testing::ExitedWithCode(0), "");`.

When an `EXPECT_EXIT` check fails, gtest makes sure to print stderr from the
child process with the unexpected exit code (which is where the test results
passed through ScopedTestResultReporter were printed).

Differential Revision: https://phabricator.services.mozilla.com/D175139
This commit is contained in:
Andreas Pehrson 2023-04-14 17:01:14 +00:00
Родитель b7d19f4f6d
Коммит 6364d695b5
2 изменённых файлов: 111 добавлений и 0 удалений

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

@ -1,5 +1,11 @@
#include "MozHelpers.h"
#include <iostream>
#include "gtest/gtest-spi.h"
#include "mozilla/Mutex.h"
#include "nsDebug.h"
namespace mozilla::gtest {
void DisableCrashReporter() {
@ -10,4 +16,58 @@ void DisableCrashReporter() {
}
}
class ScopedTestResultReporterImpl
: public ScopedTestResultReporter,
public testing::ScopedFakeTestPartResultReporter {
public:
explicit ScopedTestResultReporterImpl(ExitMode aExitMode)
: testing::ScopedFakeTestPartResultReporter(INTERCEPT_ALL_THREADS,
nullptr),
mExitMode(aExitMode) {}
~ScopedTestResultReporterImpl() {
switch (mExitMode) {
case ExitMode::ExitOnDtor:
exit(ExitCode(Status()));
case ExitMode::NoExit:
break;
}
}
void ReportTestPartResult(const testing::TestPartResult& aResult) override {
{
MutexAutoLock lock(mMutex);
if (aResult.nonfatally_failed() &&
mStatus < TestResultStatus::NonFatalFailure) {
mStatus = TestResultStatus::NonFatalFailure;
}
if (aResult.fatally_failed() &&
mStatus < TestResultStatus::FatalFailure) {
mStatus = TestResultStatus::FatalFailure;
}
}
std::ostringstream stream;
stream << aResult;
printf_stderr("%s\n", stream.str().c_str());
}
TestResultStatus Status() const override {
MutexAutoLock lock(mMutex);
return mStatus;
}
private:
const ExitMode mExitMode;
mutable Mutex mMutex{"ScopedTestResultReporterImpl::mMutex"};
TestResultStatus mStatus MOZ_GUARDED_BY(mMutex) = TestResultStatus::Pass;
};
UniquePtr<ScopedTestResultReporter> ScopedTestResultReporter::Create(
ExitMode aExitMode) {
return MakeUnique<ScopedTestResultReporterImpl>(aExitMode);
}
} // namespace mozilla::gtest

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

@ -3,6 +3,7 @@
#include "gtest/gtest.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
#include "nsServiceManagerUtils.h"
#include "nsICrashReporter.h"
@ -63,6 +64,56 @@ namespace mozilla::gtest {
void DisableCrashReporter();
/**
* Exit mode used for ScopedTestResultReporter.
*/
enum class ExitMode {
// The user of the reporter handles exit.
NoExit,
// As the reporter goes out of scope, exit with ExitCode().
ExitOnDtor,
};
/**
* Status used by ScopedTestResultReporter.
*/
enum class TestResultStatus : int {
Pass = 0,
NonFatalFailure = 1,
FatalFailure = 2,
};
inline int ExitCode(TestResultStatus aStatus) {
return static_cast<int>(aStatus);
}
/**
* This is a helper that reports test results to stderr in death test child
* processes, since that is normally disabled by default (with no way of
* enabling).
*
* Note that for this to work as intended the death test child has to, on
* failure, exit with an exit code that is unexpected to the death test parent,
* so the parent can mark the test case as failed.
*
* If the parent expects a graceful exit (code 0), ExitCode() can be used with
* Status() to exit the child process.
*
* For simplicity the reporter can exit automatically as it goes out of scope,
* when created with ExitMode::ExitOnDtor.
*/
class ScopedTestResultReporter {
public:
virtual ~ScopedTestResultReporter() = default;
/**
* The aggregate status of all observed test results.
*/
virtual TestResultStatus Status() const = 0;
static UniquePtr<ScopedTestResultReporter> Create(ExitMode aExitMode);
};
} // namespace mozilla::gtest
#endif // TESTING_GTEST_MOZILLA_HELPERS_H_