Skip to content

Commit

Permalink
Add parseUInt utility function
Browse files Browse the repository at this point in the history
There is an increasing number of places where Catch2 wants to parse
strings into numbers, but being stuck in C++14 world, we do not
have good stdlib facilities to do this (`strtoul` and `stoul`
are both bad).
  • Loading branch information
horenmar committed Oct 20, 2022
1 parent 38d9260 commit de600eb
Show file tree
Hide file tree
Showing 23 changed files with 543 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Expand Up @@ -81,6 +81,7 @@ set(INTERNAL_HEADERS
${SOURCES_DIR}/internal/catch_istream.hpp
${SOURCES_DIR}/internal/catch_unique_name.hpp
${SOURCES_DIR}/internal/catch_sharding.hpp
${SOURCES_DIR}/internal/catch_parse_numbers.hpp
${SOURCES_DIR}/generators/catch_generator_exception.hpp
${SOURCES_DIR}/generators/catch_generators.hpp
${SOURCES_DIR}/generators/catch_generators_adapters.hpp
Expand Down Expand Up @@ -184,6 +185,7 @@ set(IMPL_SOURCES
${SOURCES_DIR}/internal/catch_fatal_condition_handler.cpp
${SOURCES_DIR}/internal/catch_floating_point_helpers.cpp
${SOURCES_DIR}/internal/catch_istream.cpp
${SOURCES_DIR}/internal/catch_parse_numbers.cpp
${SOURCES_DIR}/interfaces/catch_interfaces_generatortracker.cpp
${SOURCES_DIR}/interfaces/catch_interfaces_reporter.cpp
${SOURCES_DIR}/internal/catch_list.cpp
Expand Down
1 change: 1 addition & 0 deletions src/catch2/catch_all.hpp
Expand Up @@ -78,6 +78,7 @@
#include <catch2/internal/catch_noncopyable.hpp>
#include <catch2/internal/catch_optional.hpp>
#include <catch2/internal/catch_output_redirect.hpp>
#include <catch2/internal/catch_parse_numbers.hpp>
#include <catch2/internal/catch_platform.hpp>
#include <catch2/internal/catch_polyfills.hpp>
#include <catch2/internal/catch_preprocessor.hpp>
Expand Down
49 changes: 49 additions & 0 deletions src/catch2/internal/catch_parse_numbers.cpp
@@ -0,0 +1,49 @@

// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0

#include <catch2/internal/catch_parse_numbers.hpp>

#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <catch2/internal/catch_string_manip.hpp>

#include <limits>

namespace Catch {

Optional<unsigned int> parseUInt(std::string const& input, int base) {
auto trimmed = trim( input );
// std::stoull is annoying and accepts numbers starting with '-',
// it just negates them into unsigned int
if ( trimmed.empty() || trimmed[0] == '-' ) {
return {};
}

CATCH_TRY {
size_t pos = 0;
const auto ret = std::stoull( trimmed, &pos, base );

// We did not consume the whole input, so there is an issue
// This can be bunch of different stuff, like multiple numbers
// in the input, or invalid digits/characters and so on. Either
// way, we do not want to return the partially parsed result.
if ( pos != trimmed.size() ) {
return {};
}
// Too large
if ( ret > std::numeric_limits<unsigned int>::max() ) {
return {};
}
return static_cast<unsigned int>(ret);
} CATCH_CATCH_ANON( std::exception const& ) {
// There was a larger issue with the input, e.g. the parsed
// number would be too large to fit within ull.
return {};
}
}

} // namespace Catch
22 changes: 22 additions & 0 deletions src/catch2/internal/catch_parse_numbers.hpp
@@ -0,0 +1,22 @@

// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0

#include <catch2/internal/catch_optional.hpp>

#include <string>

namespace Catch {

/**
* Parses unsigned int from the input, using provided base
*
* Effectively a wrapper around std::stoul but with better error checking
* e.g. "-1" is rejected, instead of being parsed as UINT_MAX.
*/
Optional<unsigned int> parseUInt(std::string const& input, int base = 10);
}
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Expand Up @@ -85,6 +85,7 @@ set(TEST_SOURCES
${SELF_TEST_DIR}/IntrospectiveTests/FloatingPoint.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/GeneratorsImpl.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/InternalBenchmark.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/Parse.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/PartTracker.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/RandomNumberGeneration.tests.cpp
${SELF_TEST_DIR}/IntrospectiveTests/Reporters.tests.cpp
Expand Down
1 change: 1 addition & 0 deletions tests/SelfTest/Baselines/automake.sw.approved.txt
Expand Up @@ -189,6 +189,7 @@ Nor would this
:test-result: FAIL Output from all sections is reported
:test-result: PASS Overloaded comma or address-of operators are not used
:test-result: PASS Parse test names and tags
:test-result: PASS Parse uints
:test-result: PASS Parsed tags are matched case insensitive
:test-result: PASS Parsing sharding-related cli flags
:test-result: PASS Parsing tags with non-alphabetical characters is pass-through
Expand Down
1 change: 1 addition & 0 deletions tests/SelfTest/Baselines/automake.sw.multi.approved.txt
Expand Up @@ -187,6 +187,7 @@
:test-result: FAIL Output from all sections is reported
:test-result: PASS Overloaded comma or address-of operators are not used
:test-result: PASS Parse test names and tags
:test-result: PASS Parse uints
:test-result: PASS Parsed tags are matched case insensitive
:test-result: PASS Parsing sharding-related cli flags
:test-result: PASS Parsing tags with non-alphabetical characters is pass-through
Expand Down
11 changes: 11 additions & 0 deletions tests/SelfTest/Baselines/compact.sw.approved.txt
Expand Up @@ -1270,6 +1270,17 @@ CmdLine.tests.cpp:<line number>: passed: !(spec.matches(*fakeTestCase("hidden an
CmdLine.tests.cpp:<line number>: passed: !(spec.matches(*fakeTestCase("only foo", "[foo]"))) for: !false
CmdLine.tests.cpp:<line number>: passed: !(spec.matches(*fakeTestCase("only hidden", "[.]"))) for: !false
CmdLine.tests.cpp:<line number>: passed: spec.matches(*fakeTestCase("neither foo nor hidden", "[bar]")) for: true
Parse.tests.cpp:<line number>: passed: parseUInt( "0" ) == Optional<unsigned int>{ 0 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: parseUInt( "100" ) == Optional<unsigned int>{ 100 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: parseUInt( "4294967295" ) == Optional<unsigned int>{ 4294967295 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: parseUInt( "0x<hex digits>", 16 ) == Optional<unsigned int>{ 255 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "!!KJHF*#" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "-1" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "4294967296" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "42949672964294967296429496729642949672964294967296" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "2 4" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "0x<hex digits>", 10 )) for: !{?}
TestSpecParser.tests.cpp:<line number>: passed: spec.hasFilters() for: true
TestSpecParser.tests.cpp:<line number>: passed: spec.getInvalidSpecs().empty() for: true
TestSpecParser.tests.cpp:<line number>: passed: spec.matches( testCase ) for: true
Expand Down
11 changes: 11 additions & 0 deletions tests/SelfTest/Baselines/compact.sw.multi.approved.txt
Expand Up @@ -1268,6 +1268,17 @@ CmdLine.tests.cpp:<line number>: passed: !(spec.matches(*fakeTestCase("hidden an
CmdLine.tests.cpp:<line number>: passed: !(spec.matches(*fakeTestCase("only foo", "[foo]"))) for: !false
CmdLine.tests.cpp:<line number>: passed: !(spec.matches(*fakeTestCase("only hidden", "[.]"))) for: !false
CmdLine.tests.cpp:<line number>: passed: spec.matches(*fakeTestCase("neither foo nor hidden", "[bar]")) for: true
Parse.tests.cpp:<line number>: passed: parseUInt( "0" ) == Optional<unsigned int>{ 0 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: parseUInt( "100" ) == Optional<unsigned int>{ 100 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: parseUInt( "4294967295" ) == Optional<unsigned int>{ 4294967295 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: parseUInt( "0x<hex digits>", 16 ) == Optional<unsigned int>{ 255 } for: {?} == {?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "!!KJHF*#" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "-1" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "4294967296" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "42949672964294967296429496729642949672964294967296" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "2 4" )) for: !{?}
Parse.tests.cpp:<line number>: passed: !(parseUInt( "0x<hex digits>", 10 )) for: !{?}
TestSpecParser.tests.cpp:<line number>: passed: spec.hasFilters() for: true
TestSpecParser.tests.cpp:<line number>: passed: spec.getInvalidSpecs().empty() for: true
TestSpecParser.tests.cpp:<line number>: passed: spec.matches( testCase ) for: true
Expand Down
4 changes: 2 additions & 2 deletions tests/SelfTest/Baselines/console.std.approved.txt
Expand Up @@ -1394,6 +1394,6 @@ due to unexpected exception with message:
Why would you throw a std::string?

===============================================================================
test cases: 394 | 318 passed | 69 failed | 7 failed as expected
assertions: 2284 | 2129 passed | 128 failed | 27 failed as expected
test cases: 395 | 319 passed | 69 failed | 7 failed as expected
assertions: 2295 | 2140 passed | 128 failed | 27 failed as expected

73 changes: 71 additions & 2 deletions tests/SelfTest/Baselines/console.sw.approved.txt
Expand Up @@ -9055,6 +9055,75 @@ CmdLine.tests.cpp:<line number>: PASSED:
with expansion:
true

-------------------------------------------------------------------------------
Parse uints
proper inputs
-------------------------------------------------------------------------------
Parse.tests.cpp:<line number>
...............................................................................

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "0" ) == Optional<unsigned int>{ 0 } )
with expansion:
{?} == {?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "100" ) == Optional<unsigned int>{ 100 } )
with expansion:
{?} == {?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "4294967295" ) == Optional<unsigned int>{ 4294967295 } )
with expansion:
{?} == {?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "0x<hex digits>", 16 ) == Optional<unsigned int>{ 255 } )
with expansion:
{?} == {?}

-------------------------------------------------------------------------------
Parse uints
Bad inputs
-------------------------------------------------------------------------------
Parse.tests.cpp:<line number>
...............................................................................

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "!!KJHF*#" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "-1" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "4294967296" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "42949672964294967296429496729642949672964294967296" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "2 4" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "0x<hex digits>", 10 ) )
with expansion:
!{?}

-------------------------------------------------------------------------------
Parsed tags are matched case insensitive
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -18450,6 +18519,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED:

===============================================================================
test cases: 394 | 304 passed | 83 failed | 7 failed as expected
assertions: 2299 | 2129 passed | 143 failed | 27 failed as expected
test cases: 395 | 305 passed | 83 failed | 7 failed as expected
assertions: 2310 | 2140 passed | 143 failed | 27 failed as expected

73 changes: 71 additions & 2 deletions tests/SelfTest/Baselines/console.sw.multi.approved.txt
Expand Up @@ -9053,6 +9053,75 @@ CmdLine.tests.cpp:<line number>: PASSED:
with expansion:
true

-------------------------------------------------------------------------------
Parse uints
proper inputs
-------------------------------------------------------------------------------
Parse.tests.cpp:<line number>
...............................................................................

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "0" ) == Optional<unsigned int>{ 0 } )
with expansion:
{?} == {?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "100" ) == Optional<unsigned int>{ 100 } )
with expansion:
{?} == {?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "4294967295" ) == Optional<unsigned int>{ 4294967295 } )
with expansion:
{?} == {?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE( parseUInt( "0x<hex digits>", 16 ) == Optional<unsigned int>{ 255 } )
with expansion:
{?} == {?}

-------------------------------------------------------------------------------
Parse uints
Bad inputs
-------------------------------------------------------------------------------
Parse.tests.cpp:<line number>
...............................................................................

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "!!KJHF*#" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "-1" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "4294967296" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "42949672964294967296429496729642949672964294967296" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "2 4" ) )
with expansion:
!{?}

Parse.tests.cpp:<line number>: PASSED:
REQUIRE_FALSE( parseUInt( "0x<hex digits>", 10 ) )
with expansion:
!{?}

-------------------------------------------------------------------------------
Parsed tags are matched case insensitive
-------------------------------------------------------------------------------
Expand Down Expand Up @@ -18442,6 +18511,6 @@ Misc.tests.cpp:<line number>
Misc.tests.cpp:<line number>: PASSED:

===============================================================================
test cases: 394 | 304 passed | 83 failed | 7 failed as expected
assertions: 2299 | 2129 passed | 143 failed | 27 failed as expected
test cases: 395 | 305 passed | 83 failed | 7 failed as expected
assertions: 2310 | 2140 passed | 143 failed | 27 failed as expected

4 changes: 3 additions & 1 deletion tests/SelfTest/Baselines/junit.sw.approved.txt
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuitesloose text artifact
>
<testsuite name="<exe-name>" errors="17" failures="126" tests="2299" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="126" tests="2310" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="random-seed" value="1"/>
<property name="filters" value="~[!nonportable]~[!benchmark]~[approvals] *"/>
Expand Down Expand Up @@ -1083,6 +1083,8 @@ Message.tests.cpp:<line number>
<testcase classname="<exe-name>.global" name="Parse test names and tags/Leading and trailing spaces in test name" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse test names and tags/Shortened hide tags are split apart when parsing" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse test names and tags/Shortened hide tags also properly handle exclusion" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse uints/proper inputs" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse uints/Bad inputs" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parsed tags are matched case insensitive" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parsing sharding-related cli flags/shard-count" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parsing sharding-related cli flags/Negative shard count reports error" time="{duration}" status="run"/>
Expand Down
4 changes: 3 additions & 1 deletion tests/SelfTest/Baselines/junit.sw.multi.approved.txt
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="<exe-name>" errors="17" failures="126" tests="2299" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<testsuite name="<exe-name>" errors="17" failures="126" tests="2310" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
<properties>
<property name="random-seed" value="1"/>
<property name="filters" value="~[!nonportable]~[!benchmark]~[approvals] *"/>
Expand Down Expand Up @@ -1082,6 +1082,8 @@ Message.tests.cpp:<line number>
<testcase classname="<exe-name>.global" name="Parse test names and tags/Leading and trailing spaces in test name" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse test names and tags/Shortened hide tags are split apart when parsing" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse test names and tags/Shortened hide tags also properly handle exclusion" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse uints/proper inputs" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parse uints/Bad inputs" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parsed tags are matched case insensitive" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parsing sharding-related cli flags/shard-count" time="{duration}" status="run"/>
<testcase classname="<exe-name>.global" name="Parsing sharding-related cli flags/Negative shard count reports error" time="{duration}" status="run"/>
Expand Down
4 changes: 4 additions & 0 deletions tests/SelfTest/Baselines/sonarqube.sw.approved.txt
Expand Up @@ -157,6 +157,10 @@
<testCase name="warmup" duration="{duration}"/>
<testCase name="weighted_average_quantile" duration="{duration}"/>
</file>
<file path="tests/<exe-name>/IntrospectiveTests/Parse.tests.cpp">
<testCase name="Parse uints/proper inputs" duration="{duration}"/>
<testCase name="Parse uints/Bad inputs" duration="{duration}"/>
</file>
<file path="tests/<exe-name>/IntrospectiveTests/PartTracker.tests.cpp">
<testCase name="#1938 - GENERATE after a section/A" duration="{duration}"/>
<testCase name="#1938 - GENERATE after a section/B" duration="{duration}"/>
Expand Down
4 changes: 4 additions & 0 deletions tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt
Expand Up @@ -156,6 +156,10 @@
<testCase name="warmup" duration="{duration}"/>
<testCase name="weighted_average_quantile" duration="{duration}"/>
</file>
<file path="tests/<exe-name>/IntrospectiveTests/Parse.tests.cpp">
<testCase name="Parse uints/proper inputs" duration="{duration}"/>
<testCase name="Parse uints/Bad inputs" duration="{duration}"/>
</file>
<file path="tests/<exe-name>/IntrospectiveTests/PartTracker.tests.cpp">
<testCase name="#1938 - GENERATE after a section/A" duration="{duration}"/>
<testCase name="#1938 - GENERATE after a section/B" duration="{duration}"/>
Expand Down

0 comments on commit de600eb

Please sign in to comment.