Floating-point exception observer for unit tests

This CL adds a simple tool that let a unit test fail if a floating
point exception occurs. It is possible to focus on specific exceptions.
Note that FloatingPointExceptionObserver is only effective in debug
mode. For this reason, the related unit tests only run in debug mode.
Plus, due to some platform-specific limitations, not all the floating
point exceptions are available on Android.

Bug: webrtc:8948
Change-Id: I0956e27f2f3aa68771dd647169fba7968ccbd771
Reviewed-on: https://webrtc-review.googlesource.com/58097
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Patrik Höglund <phoglund@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22768}
This commit is contained in:
Alessio Bazzica 2018-04-06 10:49:41 +02:00 committed by Commit Bot
parent 634a777b9d
commit 3fb3939896
3 changed files with 212 additions and 0 deletions

View File

@ -207,6 +207,17 @@ rtc_source_set("test_support") {
}
if (rtc_include_tests) {
rtc_source_set("floating_point_except_observer") {
testonly = true
sources = [
"fpe_observer.h",
]
deps = [
":test_support",
"../rtc_base:rtc_base_approved",
]
}
rtc_source_set("test_main") {
visibility = [ "*" ]
testonly = true
@ -317,8 +328,10 @@ if (rtc_include_tests) {
rtc_test("test_support_unittests") {
deps = [
":fileutils",
":floating_point_except_observer",
":perf_test",
":rtp_test_utils",
":test_support",
"../api:video_frame_api",
"../api:video_frame_api_i420",
"../call:call_interfaces",
@ -329,6 +342,7 @@ if (rtc_include_tests) {
"../system_wrappers",
]
sources = [
"fpe_observer_unittest.cc",
"frame_generator_unittest.cc",
"rtp_file_reader_unittest.cc",
"rtp_file_writer_unittest.cc",

60
test/fpe_observer.h Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef TEST_FPE_OBSERVER_H_
#define TEST_FPE_OBSERVER_H_
#include <cfenv>
#include "test/gtest.h"
namespace webrtc {
namespace test {
// Class that let a unit test fail if floating point exceptions are signaled.
// Usage:
// {
// FloatingPointExceptionObserver fpe_observer;
// ...
// }
class FloatingPointExceptionObserver {
public:
FloatingPointExceptionObserver(int mask = FE_DIVBYZERO | FE_INVALID |
FE_OVERFLOW | FE_UNDERFLOW)
: mask_(mask) {
#ifdef NDEBUG
EXPECT_LE(0, mask_); // Avoid compile time errors in release mode.
#else
EXPECT_EQ(0, std::feclearexcept(mask_));
#endif
}
~FloatingPointExceptionObserver() {
#ifndef NDEBUG
const int occurred = std::fetestexcept(mask_);
EXPECT_FALSE(occurred & FE_INVALID)
<< "Domain error occurred in a floating-point operation.";
EXPECT_FALSE(occurred & FE_DIVBYZERO) << "Division by zero.";
EXPECT_FALSE(occurred & FE_OVERFLOW)
<< "The result of a floating-point operation was too large.";
EXPECT_FALSE(occurred & FE_UNDERFLOW)
<< "The result of a floating-point operation was subnormal with a loss "
<< "of precision.";
EXPECT_FALSE(occurred & FE_INEXACT)
<< "Inexact result: rounding during a floating-point operation.";
#endif
}
private:
const int mask_;
};
} // namespace test
} // namespace webrtc
#endif // TEST_FPE_OBSERVER_H_

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <cmath>
#include <iostream>
#include <limits>
#include <map>
#include <string>
#include "rtc_base/logging.h"
#include "test/fpe_observer.h"
#include "test/gtest.h"
namespace webrtc {
namespace test {
namespace {
std::map<int, std::string> GetExceptionCodes() {
static const std::map<int, std::string> codes = {
{FE_INVALID, "FE_INVALID"},
// TODO(bugs.webrtc.org/8948): Some floating point exceptions are not signaled
// on Android.
#ifndef WEBRTC_ANDROID
{FE_DIVBYZERO, "FE_DIVBYZERO"}, {FE_OVERFLOW, "FE_OVERFLOW"},
{FE_UNDERFLOW, "FE_UNDERFLOW"},
#endif
{FE_INEXACT, "FE_INEXACT"},
};
return codes;
}
// Define helper functions as a trick to trigger floating point exceptions at
// run-time.
float MinusOne() {
return -std::cos(0.f);
}
float PlusOne() {
return std::cos(0.f);
}
float PlusTwo() {
return 2.f * std::cos(0.f);
}
// Triggers one or more exception according to the |trigger| mask while
// observing the floating point exceptions defined in the |observe| mask.
void TriggerObserveFloatingPointExceptions(int trigger, int observe) {
FloatingPointExceptionObserver fpe_observer(observe);
float tmp = 0.f;
if (trigger & FE_INVALID)
tmp = std::sqrt(MinusOne());
if (trigger & FE_DIVBYZERO)
tmp = 1.f / (MinusOne() + PlusOne());
if (trigger & FE_OVERFLOW)
tmp = std::numeric_limits<float>::max() * PlusTwo();
if (trigger & FE_UNDERFLOW) {
// TODO(bugs.webrtc.org/8948): Check why FE_UNDERFLOW is not triggered with
// <float>.
tmp = std::numeric_limits<double>::min() / PlusTwo();
}
if (trigger & FE_INEXACT) {
tmp = std::sqrt(2.0);
}
}
} // namespace
TEST(FloatingPointExceptionObserverTest, CheckTestConstants) {
// Check that the constants used in the test suite behave as expected.
ASSERT_EQ(0.f, MinusOne() + PlusOne());
#ifndef WEBRTC_ANDROID
// Check that all the floating point exceptions are exercised.
int all_flags = 0;
for (const auto v : GetExceptionCodes()) {
RTC_LOG(LS_INFO) << v.second << " = " << v.first;
all_flags |= v.first;
}
#ifdef WEBRTC_MAC
#ifndef FE_UNNORMAL
#define FE_UNNORMAL 2
#endif
all_flags |= FE_UNNORMAL; // Non standard OS specific flag.
#endif
ASSERT_EQ(FE_ALL_EXCEPT, all_flags);
#endif
}
#ifdef NDEBUG
#define MAYBE_CheckNoFalsePositives DISABLED_CheckNoFalsePositives
#define MAYBE_CheckNoFalseNegatives DISABLED_CheckNoFalseNegatives
#else
#define MAYBE_CheckNoFalsePositives CheckNoFalsePositives
#define MAYBE_CheckNoFalseNegatives CheckNoFalseNegatives
#endif
// The floating point exception observer only works in debug mode.
// Trigger each single floating point exception while observing all the other
// exceptions. It must not fail.
TEST(FloatingPointExceptionObserverTest, MAYBE_CheckNoFalsePositives) {
for (const auto exception_code : GetExceptionCodes()) {
SCOPED_TRACE(exception_code.second);
const int trigger = exception_code.first;
int observe = FE_ALL_EXCEPT & ~trigger;
// Over/underflows also trigger FE_INEXACT; hence, ignore FE_INEXACT (which
// would be a false positive).
if (trigger & (FE_OVERFLOW | FE_UNDERFLOW))
observe &= ~FE_INEXACT;
TriggerObserveFloatingPointExceptions(trigger, observe);
}
}
// Trigger each single floating point exception while observing it. Check that
// this fails.
TEST(FloatingPointExceptionObserverTest, MAYBE_CheckNoFalseNegatives) {
for (const auto exception_code : GetExceptionCodes()) {
SCOPED_TRACE(exception_code.second);
const int trigger = exception_code.first;
#ifdef WEBRTC_ANDROID
// TODO(bugs.webrtc.org/8948): FE_INEXACT is not triggered on Android.
if (trigger == FE_INEXACT)
continue;
#endif
EXPECT_NONFATAL_FAILURE(
TriggerObserveFloatingPointExceptions(trigger, trigger), "");
}
}
} // namespace test
} // namespace webrtc