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:
parent
634a777b9d
commit
3fb3939896
@ -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
60
test/fpe_observer.h
Normal 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_
|
||||
138
test/fpe_observer_unittest.cc
Normal file
138
test/fpe_observer_unittest.cc
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user