Merge pull request #1614 from mlimber/master

Allow custom precision in error reports for floating-point numbers
This commit is contained in:
Martin Hořeňovský 2019-05-03 17:46:03 +02:00 committed by GitHub
commit 92ad9ee355
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 150 additions and 9 deletions

View File

@ -7,6 +7,8 @@
[Catch::is_range specialisation](#catchis_range-specialisation)<br> [Catch::is_range specialisation](#catchis_range-specialisation)<br>
[Exceptions](#exceptions)<br> [Exceptions](#exceptions)<br>
[Enums](#enums)<br> [Enums](#enums)<br>
[Floating point precision](#floating-point-precision)<br>
Catch needs to be able to convert types you use in assertions and logging expressions into strings (for logging and reporting purposes). Catch needs to be able to convert types you use in assertions and logging expressions into strings (for logging and reporting purposes).
Most built-in or std types are supported out of the box but there are two ways that you can tell Catch how to convert your own types (or other, third-party types) into strings. Most built-in or std types are supported out of the box but there are two ways that you can tell Catch how to convert your own types (or other, third-party types) into strings.
@ -104,6 +106,22 @@ TEST_CASE() {
} }
``` ```
## Floating point precision
Catch provides a built-in `StringMaker` specialization for both `float`
`double`. By default, it uses what we think is a reasonable precision,
but you can customize it by modifying the `precision` static variable
inside the `StringMaker` specialization, like so:
```cpp
Catch::StringMaker<float>::precision = 15;
const float testFloat1 = 1.12345678901234567899f;
const float testFloat2 = 1.12345678991234567899f;
REQUIRE(testFloat1 == testFloat2);
```
This assertion will fail and print out the `testFloat1` and `testFloat2`
to 15 decimal places.
--- ---

View File

@ -234,11 +234,16 @@ std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) {
return "nullptr"; return "nullptr";
} }
int StringMaker<float>::precision = 5;
std::string StringMaker<float>::convert(float value) { std::string StringMaker<float>::convert(float value) {
return fpToString(value, 5) + 'f'; return fpToString(value, precision) + 'f';
} }
int StringMaker<double>::precision = 10;
std::string StringMaker<double>::convert(double value) { std::string StringMaker<double>::convert(double value) {
return fpToString(value, 10); return fpToString(value, precision);
} }
std::string ratio_string<std::atto>::symbol() { return "a"; } std::string ratio_string<std::atto>::symbol() { return "a"; }

View File

@ -261,10 +261,13 @@ namespace Catch {
template<> template<>
struct StringMaker<float> { struct StringMaker<float> {
static std::string convert(float value); static std::string convert(float value);
static int precision;
}; };
template<> template<>
struct StringMaker<double> { struct StringMaker<double> {
static std::string convert(double value); static std::string convert(double value);
static int precision;
}; };
template <typename T> template <typename T>

View File

@ -859,6 +859,10 @@ Condition.tests.cpp:<line number>: passed: cpc != 0 for: 0x<hex digits> != 0
Condition.tests.cpp:<line number>: passed: returnsNull() == 0 for: {null string} == 0 Condition.tests.cpp:<line number>: passed: returnsNull() == 0 for: {null string} == 0
Condition.tests.cpp:<line number>: passed: returnsConstNull() == 0 for: {null string} == 0 Condition.tests.cpp:<line number>: passed: returnsConstNull() == 0 for: {null string} == 0
Condition.tests.cpp:<line number>: passed: 0 != p for: 0 != 0x<hex digits> Condition.tests.cpp:<line number>: passed: 0 != p for: 0 != 0x<hex digits>
ToStringGeneral.tests.cpp:<line number>: passed: str1.size() == 3 + 5 for: 8 == 8
ToStringGeneral.tests.cpp:<line number>: passed: str2.size() == 3 + 10 for: 13 == 13
ToStringGeneral.tests.cpp:<line number>: passed: str1.size() == 2 + 5 for: 7 == 7
ToStringGeneral.tests.cpp:<line number>: passed: str2.size() == 2 + 15 for: 17 == 17
Matchers.tests.cpp:<line number>: passed: "foo", Predicate<const char*>([] (const char* const&) { return true; }) for: "foo" matches undescribed predicate Matchers.tests.cpp:<line number>: passed: "foo", Predicate<const char*>([] (const char* const&) { return true; }) for: "foo" matches undescribed predicate
CmdLine.tests.cpp:<line number>: passed: result for: {?} CmdLine.tests.cpp:<line number>: passed: result for: {?}
CmdLine.tests.cpp:<line number>: passed: config.processName == "" for: "" == "" CmdLine.tests.cpp:<line number>: passed: config.processName == "" for: "" == ""

View File

@ -1299,6 +1299,6 @@ due to unexpected exception with message:
Why would you throw a std::string? Why would you throw a std::string?
=============================================================================== ===============================================================================
test cases: 266 | 199 passed | 63 failed | 4 failed as expected test cases: 267 | 200 passed | 63 failed | 4 failed as expected
assertions: 1449 | 1304 passed | 124 failed | 21 failed as expected assertions: 1453 | 1308 passed | 124 failed | 21 failed as expected

View File

@ -6192,6 +6192,40 @@ Condition.tests.cpp:<line number>: PASSED:
with expansion: with expansion:
0 != 0x<hex digits> 0 != 0x<hex digits>
-------------------------------------------------------------------------------
Precision of floating point stringification can be set
Floats
-------------------------------------------------------------------------------
ToStringGeneral.tests.cpp:<line number>
...............................................................................
ToStringGeneral.tests.cpp:<line number>: PASSED:
CHECK( str1.size() == 3 + 5 )
with expansion:
8 == 8
ToStringGeneral.tests.cpp:<line number>: PASSED:
REQUIRE( str2.size() == 3 + 10 )
with expansion:
13 == 13
-------------------------------------------------------------------------------
Precision of floating point stringification can be set
Double
-------------------------------------------------------------------------------
ToStringGeneral.tests.cpp:<line number>
...............................................................................
ToStringGeneral.tests.cpp:<line number>: PASSED:
CHECK( str1.size() == 2 + 5 )
with expansion:
7 == 7
ToStringGeneral.tests.cpp:<line number>: PASSED:
REQUIRE( str2.size() == 2 + 15 )
with expansion:
17 == 17
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Predicate matcher can accept const char* Predicate matcher can accept const char*
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -11389,6 +11423,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED: Misc.tests.cpp:<line number>: PASSED:
=============================================================================== ===============================================================================
test cases: 266 | 183 passed | 79 failed | 4 failed as expected test cases: 267 | 184 passed | 79 failed | 4 failed as expected
assertions: 1466 | 1304 passed | 141 failed | 21 failed as expected assertions: 1470 | 1308 passed | 141 failed | 21 failed as expected

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<testsuitesloose text artifact <testsuitesloose text artifact
> >
<testsuite name="<exe-name>" errors="17" failures="125" tests="1467" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}"> <testsuite name="<exe-name>" errors="17" failures="125" tests="1471" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties> <properties>
<property name="filters" value="~[!nonportable]~[!benchmark]~[approvals]"/> <property name="filters" value="~[!nonportable]~[!benchmark]~[approvals]"/>
<property name="random-seed" value="1"/> <property name="random-seed" value="1"/>
@ -574,6 +574,8 @@ Message.tests.cpp:<line number>
<testcase classname="<exe-name>.global" name="Parse test names and tags/empty quoted name" time="{duration}"/> <testcase classname="<exe-name>.global" name="Parse test names and tags/empty quoted name" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Parse test names and tags/quoted string followed by tag exclusion" time="{duration}"/> <testcase classname="<exe-name>.global" name="Parse test names and tags/quoted string followed by tag exclusion" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Pointers can be compared to null" time="{duration}"/> <testcase classname="<exe-name>.global" name="Pointers can be compared to null" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Precision of floating point stringification can be set/Floats" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Precision of floating point stringification can be set/Double" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Predicate matcher can accept const char*" time="{duration}"/> <testcase classname="<exe-name>.global" name="Predicate matcher can accept const char*" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Process can be configured on command line/empty args don't cause a crash" time="{duration}"/> <testcase classname="<exe-name>.global" name="Process can be configured on command line/empty args don't cause a crash" time="{duration}"/>
<testcase classname="<exe-name>.global" name="Process can be configured on command line/default - no arguments" time="{duration}"/> <testcase classname="<exe-name>.global" name="Process can be configured on command line/default - no arguments" time="{duration}"/>

View File

@ -7778,6 +7778,47 @@ Nor would this
</Expression> </Expression>
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<TestCase name="Precision of floating point stringification can be set" tags="[floatingPoint][toString]" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Section name="Floats" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Expression success="true" type="CHECK" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str1.size() == 3 + 5
</Original>
<Expanded>
8 == 8
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str2.size() == 3 + 10
</Original>
<Expanded>
13 == 13
</Expanded>
</Expression>
<OverallResults successes="2" failures="0" expectedFailures="0"/>
</Section>
<Section name="Double" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Expression success="true" type="CHECK" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str1.size() == 2 + 5
</Original>
<Expanded>
7 == 7
</Expanded>
</Expression>
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/UsageTests/ToStringGeneral.tests.cpp" >
<Original>
str2.size() == 2 + 15
</Original>
<Expanded>
17 == 17
</Expanded>
</Expression>
<OverallResults successes="2" failures="0" expectedFailures="0"/>
</Section>
<OverallResult success="true"/>
</TestCase>
<TestCase name="Predicate matcher can accept const char*" tags="[compilation][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > <TestCase name="Predicate matcher can accept const char*" tags="[compilation][matchers]" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" > <Expression success="true" type="REQUIRE_THAT" filename="projects/<exe-name>/UsageTests/Matchers.tests.cpp" >
<Original> <Original>
@ -13728,7 +13769,7 @@ loose text artifact
</Section> </Section>
<OverallResult success="true"/> <OverallResult success="true"/>
</TestCase> </TestCase>
<OverallResults successes="1304" failures="142" expectedFailures="21"/> <OverallResults successes="1308" failures="142" expectedFailures="21"/>
</Group> </Group>
<OverallResults successes="1304" failures="141" expectedFailures="21"/> <OverallResults successes="1308" failures="141" expectedFailures="21"/>
</Catch> </Catch>

View File

@ -128,6 +128,40 @@ TEST_CASE("String views are stringified like other strings", "[toString][approva
#endif #endif
TEST_CASE("Precision of floating point stringification can be set", "[toString][floatingPoint]") {
SECTION("Floats") {
using sm = Catch::StringMaker<float>;
const auto oldPrecision = sm::precision;
const float testFloat = 1.12345678901234567899f;
auto str1 = sm::convert(testFloat);
sm::precision = 5;
// "1." prefix = 2 chars, f suffix is another char
CHECK(str1.size() == 3 + 5);
sm::precision = 10;
auto str2 = sm::convert(testFloat);
REQUIRE(str2.size() == 3 + 10);
sm::precision = oldPrecision;
}
SECTION("Double") {
using sm = Catch::StringMaker<double>;
const auto oldPrecision = sm::precision;
const double testDouble = 1.123456789012345678901234567899;
sm::precision = 5;
auto str1 = sm::convert(testDouble);
// "1." prefix = 2 chars
CHECK(str1.size() == 2 + 5);
sm::precision = 15;
auto str2 = sm::convert(testDouble);
REQUIRE(str2.size() == 2 + 15);
sm::precision = oldPrecision;
}
}
namespace { namespace {
struct WhatException : std::exception { struct WhatException : std::exception {