diff --git a/include/catch.hpp b/include/catch.hpp index 017619ce..02a727cc 100644 --- a/include/catch.hpp +++ b/include/catch.hpp @@ -62,6 +62,7 @@ #ifndef CATCH_CONFIG_DISABLE_MATCHERS #include "internal/catch_capture_matchers.h" #endif +#include "internal/catch_generators.hpp" // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections diff --git a/include/internal/catch_generators.cpp b/include/internal/catch_generators.cpp new file mode 100644 index 00000000..caf5a0df --- /dev/null +++ b/include/internal/catch_generators.cpp @@ -0,0 +1,45 @@ +/* + * Created by Phil Nash on 15/6/2018. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ + +#include "catch_generators.hpp" +#include "catch_random_number_generator.h" +#include "catch_interfaces_capture.h" + +#include + +namespace Catch { + +IGeneratorTracker::~IGeneratorTracker() {} + +namespace Generators { + + GeneratorBase::~GeneratorBase() {} + + std::vector randomiseIndices( size_t selectionSize, size_t sourceSize ) { + + assert( selectionSize <= sourceSize ); + std::vector indices; + indices.reserve( selectionSize ); + std::uniform_int_distribution uid( 0, sourceSize-1 ); + + std::set seen; + // !TBD: improve this algorithm + while( indices.size() < selectionSize ) { + auto index = uid( rng() ); + if( seen.insert( index ).second ) + indices.push_back( index ); + } + return indices; + } + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + return getResultCapture().acquireGeneratorTracker( lineInfo ); + } + + +} // namespace Generators +} // namespace Catch diff --git a/include/internal/catch_generators.hpp b/include/internal/catch_generators.hpp new file mode 100644 index 00000000..26a310c2 --- /dev/null +++ b/include/internal/catch_generators.hpp @@ -0,0 +1,255 @@ +/* + * Created by Phil Nash on 15/6/2018. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include "catch_interfaces_generatortracker.h" +#include "catch_common.h" + +#include +#include +#include +#include + +#include + +namespace Catch { +namespace Generators { + + // !TBD move this into its own location? + namespace pf{ + template + std::unique_ptr make_unique( Args&&... args ) { + return std::unique_ptr(new T(std::forward(args)...)); + } + } + + template + struct IGenerator { + virtual ~IGenerator() {} + virtual auto get( size_t index ) const -> T = 0; + }; + + template + class SingleValueGenerator : public IGenerator { + T m_value; + public: + SingleValueGenerator( T const& value ) : m_value( value ) {} + + auto get( size_t ) const -> T override { + return m_value; + } + }; + + template + class FixedValuesGenerator : public IGenerator { + std::vector m_values; + + public: + FixedValuesGenerator( std::initializer_list values ) : m_values( values ) {} + + auto get( size_t index ) const -> T override { + return m_values[index]; + } + }; + + template + class RangeGenerator : public IGenerator { + T const m_first; + T const m_last; + + public: + RangeGenerator( T const& first, T const& last ) : m_first( first ), m_last( last ) { + assert( m_last > m_first ); + } + + auto get( size_t index ) const -> T override { + // ToDo:: introduce a safe cast to catch potential overflows + return static_cast(m_first+index); + } + }; + + + template + struct NullGenerator : IGenerator { + auto get( size_t ) const -> T override { + throw std::logic_error("A Null Generator should always be empty" ); + } + }; + + template + class Generator { + std::unique_ptr> m_generator; + size_t m_size; + + public: + Generator( size_t size, std::unique_ptr> generator ) + : m_generator( std::move( generator ) ), + m_size( size ) + {} + + auto size() const -> size_t { return m_size; } + auto operator[]( size_t index ) const -> T { + assert( index < m_size ); + return m_generator->get( index ); + } + }; + + std::vector randomiseIndices( size_t selectionSize, size_t sourceSize ); + + template + class GeneratorRandomiser : public IGenerator { + Generator m_baseGenerator; + + std::vector m_indices; + public: + GeneratorRandomiser( Generator&& baseGenerator, size_t numberOfItems ) + : m_baseGenerator( std::move( baseGenerator ) ), + m_indices( randomiseIndices( numberOfItems, m_baseGenerator.size() ) ) + {} + + auto get( size_t index ) const -> T override { + return m_baseGenerator[m_indices[index]]; + } + }; + + template + struct RequiresASpecialisationFor; + + template + auto all() -> Generator { return RequiresASpecialisationFor(); } + + + template + auto range( T const& first, T const& last ) -> Generator { + return Generator( (last-first), pf::make_unique>( first, last ) ); + } + template<> + inline auto all() -> Generator { + return range( std::numeric_limits::min(), std::numeric_limits::max()-1 ); + } + + + template + auto random( T const& first, T const& last ) -> Generator { + auto gen = range( first, last ); + auto size = gen.size(); + + return Generator( size, pf::make_unique>( std::move( gen ), size ) ); + } + template + auto random( size_t size ) -> Generator { + return Generator( size, pf::make_unique>( all(), size ) ); + } + + template + auto values( std::initializer_list values ) -> Generator { + return Generator( values.size(), pf::make_unique>( values ) ); + } + template + auto value( T const& val ) -> Generator { + return Generator( 1, pf::make_unique>( val ) ); + } + + template + auto as() -> Generator { + return Generator( 0, pf::make_unique>() ); + } + + template + auto table( std::initializer_list>&& tuples ) -> Generator> { + return values>( std::forward>>( tuples ) ); + } + + + template + struct Generators : GeneratorBase { + std::vector> m_generators; + + using type = T; + + Generators() : GeneratorBase( 0 ) {} + + void populate( T&& val ) { + m_size += 1; + m_generators.emplace_back( value( std::move( val ) ) ); + } + template + void populate( U&& val ) { + populate( T( std::move( val ) ) ); + } + void populate( Generator&& generator ) { + m_size += generator.size(); + m_generators.emplace_back( std::move( generator ) ); + } + + template + void populate( U&& valueOrGenerator, Gs... moreGenerators ) { + populate( std::forward( valueOrGenerator ) ); + populate( std::forward( moreGenerators )... ); + } + + auto operator[]( size_t index ) const -> T { + size_t sizes = 0; + for( auto const& gen : m_generators ) { + auto localIndex = index-sizes; + sizes += gen.size(); + if( index < sizes ) + return gen[localIndex]; + } + throw std::out_of_range("index out of range"); + } + }; + + template + auto makeGenerators( Generator&& generator, Gs... moreGenerators ) -> Generators { + Generators generators; + generators.m_generators.reserve( 1+sizeof...(Gs) ); + generators.populate( std::move( generator ), std::forward( moreGenerators )... ); + return generators; + } + template + auto makeGenerators( Generator&& generator ) -> Generators { + Generators generators; + generators.populate( std::move( generator ) ); + return generators; + } + template + auto makeGenerators( T&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( std::forward( val ) ), std::forward( moreGenerators )... ); + } + template + auto makeGenerators( U&& val, Gs... moreGenerators ) -> Generators { + return makeGenerators( value( T( std::forward( val ) ) ), std::forward( moreGenerators )... ); + } + + + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; + + template + // Note: The type after -> is weird, because VS2015 cannot parse + // the expression used in the typedef inside, when it is in + // return type. Yeah, ¯\_(ツ)_/¯ + auto generate( SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval()[0]) { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker& tracker = acquireGeneratorTracker( lineInfo ); + if( !tracker.hasGenerator() ) + tracker.setGenerator( pf::make_unique>( generatorExpression() ) ); + + auto const& generator = static_cast const&>( *tracker.getGenerator() ); + return generator[tracker.getIndex()]; + } + +} // namespace Generators +} // namespace Catch + +#define GENERATE( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_LINEINFO, []{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) + + +#endif // TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED diff --git a/include/internal/catch_interfaces_capture.h b/include/internal/catch_interfaces_capture.h index 4d091f89..950d498d 100644 --- a/include/internal/catch_interfaces_capture.h +++ b/include/internal/catch_interfaces_capture.h @@ -24,8 +24,10 @@ namespace Catch { struct BenchmarkInfo; struct BenchmarkStats; struct AssertionReaction; + struct SourceLineInfo; struct ITransientExpression; + struct IGeneratorTracker; struct IResultCapture { @@ -36,6 +38,8 @@ namespace Catch { virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + virtual auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0; + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; diff --git a/include/internal/catch_interfaces_generatortracker.h b/include/internal/catch_interfaces_generatortracker.h new file mode 100644 index 00000000..2cf6269e --- /dev/null +++ b/include/internal/catch_interfaces_generatortracker.h @@ -0,0 +1,39 @@ +/* + * Created by Phil Nash on 26/6/2018. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ + +#ifndef TWOBLUECUBES_CATCH_INTERFACES_GENERATORTRACKER_INCLUDED +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORTRACKER_INCLUDED + +#include + +namespace Catch { + + namespace Generators { + class GeneratorBase { + protected: + size_t m_size = 0; + + public: + GeneratorBase( size_t size ) : m_size( size ) {} + virtual ~GeneratorBase(); + auto size() const -> size_t { return m_size; } + }; + using GeneratorBasePtr = std::unique_ptr; + + } // namespace Generators + + struct IGeneratorTracker { + virtual ~IGeneratorTracker(); + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + virtual auto getIndex() const -> std::size_t = 0; + }; + +} // namespace Catch + +#endif //TWOBLUECUBES_CATCH_INTERFACES_GENERATORTRACKER_INCLUDED diff --git a/include/internal/catch_run_context.cpp b/include/internal/catch_run_context.cpp index ecaa34db..2739284c 100644 --- a/include/internal/catch_run_context.cpp +++ b/include/internal/catch_run_context.cpp @@ -11,6 +11,70 @@ namespace Catch { + namespace Generators { + struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker { + size_t m_index = static_cast( -1 ); + GeneratorBasePtr m_generator; + + GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + {} + ~GeneratorTracker(); + + static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) { + std::shared_ptr tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast( childTracker ); + } + else { + tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + void moveNext() { + m_index++; + m_children.clear(); + } + + // TrackerBase interface + bool isIndexTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_generator->size()-1 ) + m_runState = Executing; + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = std::move( generator ); + } + auto getIndex() const -> size_t override { + return m_index; + } + }; + GeneratorTracker::~GeneratorTracker() {} + } + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) : m_runInfo(_config->name()), m_context(getCurrentMutableContext()), @@ -128,6 +192,13 @@ namespace Catch { return true; } + auto RunContext::acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& { + using namespace Generators; + GeneratorTracker& tracker = GeneratorTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( "generator", lineInfo ) ); + assert( tracker.isOpen() ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } bool RunContext::testForMissingAssertions(Counts& assertions) { if (assertions.total() != 0) diff --git a/include/internal/catch_run_context.h b/include/internal/catch_run_context.h index 6b894173..952ef147 100644 --- a/include/internal/catch_run_context.h +++ b/include/internal/catch_run_context.h @@ -8,6 +8,7 @@ #ifndef TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED +#include "catch_interfaces_generatortracker.h" #include "catch_interfaces_runner.h" #include "catch_interfaces_reporter.h" #include "catch_interfaces_exception.h" @@ -79,6 +80,8 @@ namespace Catch { void sectionEnded( SectionEndInfo const& endInfo ) override; void sectionEndedEarly( SectionEndInfo const& endInfo ) override; + auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override; + void benchmarkStarting( BenchmarkInfo const& info ) override; void benchmarkEnded( BenchmarkStats const& stats ) override; diff --git a/include/internal/catch_test_case_tracker.cpp b/include/internal/catch_test_case_tracker.cpp index c6759438..7839a71b 100644 --- a/include/internal/catch_test_case_tracker.cpp +++ b/include/internal/catch_test_case_tracker.cpp @@ -69,14 +69,6 @@ namespace TestCaseTracking { } - - TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} - bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { - return - tracker->nameAndLocation().location == m_nameAndLocation.location && - tracker->nameAndLocation().name == m_nameAndLocation.name; - } - TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) : m_nameAndLocation( nameAndLocation ), m_ctx( ctx ), @@ -105,7 +97,12 @@ namespace TestCaseTracking { } ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { - auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + auto it = std::find_if( m_children.begin(), m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ){ + return + tracker->nameAndLocation().location == nameAndLocation.location && + tracker->nameAndLocation().name == nameAndLocation.name; + } ); return( it != m_children.end() ) ? *it : nullptr; diff --git a/include/internal/catch_test_case_tracker.h b/include/internal/catch_test_case_tracker.h index a4b0440a..72b506a9 100644 --- a/include/internal/catch_test_case_tracker.h +++ b/include/internal/catch_test_case_tracker.h @@ -95,13 +95,6 @@ namespace TestCaseTracking { Failed }; - class TrackerHasName { - NameAndLocation m_nameAndLocation; - public: - TrackerHasName( NameAndLocation const& nameAndLocation ); - bool operator ()( ITrackerPtr const& tracker ) const; - }; - using Children = std::vector; NameAndLocation m_nameAndLocation; TrackerContext& m_ctx; diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 9fd2210f..d535eec0 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -5,6 +5,7 @@ include(MiscFunctions) set(TEST_SOURCES ${SELF_TEST_DIR}/TestMain.cpp ${SELF_TEST_DIR}/IntrospectiveTests/CmdLine.tests.cpp + ${SELF_TEST_DIR}/IntrospectiveTests/GeneratorsImpl.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/PartTracker.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/TagAlias.tests.cpp ${SELF_TEST_DIR}/IntrospectiveTests/String.tests.cpp @@ -18,6 +19,7 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/UsageTests/Decomposition.tests.cpp ${SELF_TEST_DIR}/UsageTests/EnumToString.tests.cpp ${SELF_TEST_DIR}/UsageTests/Exception.tests.cpp + ${SELF_TEST_DIR}/UsageTests/Generators.tests.cpp ${SELF_TEST_DIR}/UsageTests/Message.tests.cpp ${SELF_TEST_DIR}/UsageTests/Misc.tests.cpp ${SELF_TEST_DIR}/UsageTests/ToStringChrono.tests.cpp @@ -85,6 +87,7 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_exception_translator_registry.h ${HEADER_DIR}/internal/catch_external_interfaces.h ${HEADER_DIR}/internal/catch_fatal_condition.h + ${HEADER_DIR}/internal/catch_generators.hpp ${HEADER_DIR}/internal/catch_impl.hpp ${HEADER_DIR}/internal/catch_interfaces_capture.h ${HEADER_DIR}/internal/catch_interfaces_config.h @@ -161,9 +164,11 @@ set(IMPL_SOURCES ${HEADER_DIR}/internal/catch_errno_guard.cpp ${HEADER_DIR}/internal/catch_exception_translator_registry.cpp ${HEADER_DIR}/internal/catch_fatal_condition.cpp + ${HEADER_DIR}/internal/catch_generators.cpp ${HEADER_DIR}/internal/catch_interfaces_capture.cpp ${HEADER_DIR}/internal/catch_interfaces_config.cpp ${HEADER_DIR}/internal/catch_interfaces_exception.cpp + ${HEADER_DIR}/internal/catch_interfaces_generatortracker.h ${HEADER_DIR}/internal/catch_interfaces_registry_hub.cpp ${HEADER_DIR}/internal/catch_interfaces_runner.cpp ${HEADER_DIR}/internal/catch_interfaces_testcase.cpp diff --git a/projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp b/projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp new file mode 100644 index 00000000..b3074b2e --- /dev/null +++ b/projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp @@ -0,0 +1,93 @@ +#include "catch.hpp" + +// Tests of generartor implementation details + +TEST_CASE("Generators impl", "[impl]") { + using namespace Catch::Generators; + + SECTION( "range" ) { + auto gen = range(1,3); + + CHECK( gen.size() == 2 ); + + CHECK( gen[0] == 1 ); + CHECK( gen[1] == 2 ); + } + SECTION( "fixed values" ) { + auto gen = values( { 3, 1, 4, 1 } ); + + CHECK( gen.size() == 4 ); + CHECK( gen[0] == 3 ); + CHECK( gen[1] == 1 ); + CHECK( gen[2] == 4 ); + CHECK( gen[3] == 1 ); + } + SECTION( "combined" ) { + auto gen = makeGenerators( range( 1, 3 ), values( { 9, 7 } ) ); + + CHECK( gen.size() == 4 ); + CHECK( gen[0] == 1 ); + CHECK( gen[1] == 2 ); + CHECK( gen[2] == 9 ); + CHECK( gen[3] == 7 ); + } + + SECTION( "values" ) { + auto gen = makeGenerators( 3, 1 ); + + CHECK( gen.size() == 2 ); + CHECK( gen[0] == 3 ); + CHECK( gen[1] == 1 ); + } + SECTION( "values2" ) { + auto gen = makeGenerators( 3, 1 ); + + CHECK( gen.size() == 2 ); + CHECK( gen[0] == 3 ); + CHECK( gen[1] == 1 ); + } + + + SECTION( "type erasure" ) { + auto gen = makeGenerators( range( 7, 10 ), 11 ); + + // Make type erased version + auto dynCopy = pf::make_unique>( std::move( gen ) ); + std::unique_ptr base = std::move( dynCopy ); + + // Only thing we can do is ask for the size + CHECK( base->size() == 4 ); + + // Restore typed version + auto typed = dynamic_cast const*>( base.get() ); + REQUIRE( typed ); + CHECK( typed->size() == 4 ); + CHECK( (*typed)[0] == 7 ); + CHECK( (*typed)[3] == 11 ); + } +} + +TEST_CASE("Generators impl - random", "[approvals]") { + using namespace Catch::Generators; + + SECTION( "random range" ) { + auto gen = random( 3, 9 ); + + CHECK( gen.size() == 6 ); + for( size_t i = 0; i < 6; ++i ) { + CHECK( gen[i] >= 3 ); + CHECK( gen[i] <= 8 ); + if( i > 0 ) + CHECK( gen[i] != gen[i-1] ); + } + } + SECTION( "random selection" ) { + auto gen = random( 10 ); + + CHECK( gen.size() == 10 ); + for( size_t i = 0; i < 10; ++i ) { + if( i > 0 ) + CHECK( gen[i] != gen[i-1] ); + } + } +} diff --git a/projects/SelfTest/UsageTests/Generators.tests.cpp b/projects/SelfTest/UsageTests/Generators.tests.cpp new file mode 100644 index 00000000..2600a72f --- /dev/null +++ b/projects/SelfTest/UsageTests/Generators.tests.cpp @@ -0,0 +1,134 @@ +#include "catch.hpp" + +// Examples of usage of Generators + +// This test doesn't do much - it just shows how you can have several generators, of different +// types (ie `i` and `j` are different types), can be sequenced using `,` and +// can be expressed as named generators (like range) or as individual values. +// Generators can be mixed with SECTIONs. +// At time of writing the generated values are not automatically reported as part of the test +// name or associated values - so we explicitly CAPTURE then (run this with `-s` to see them). +// We could also incorporate them into the section names using DYNAMIC_SECTION. See the BDD +// example later for more information. +TEST_CASE("Generators") { + + auto i = GENERATE( as(), "a", "b", "c" ); + + SECTION( "one" ) { + auto j = GENERATE( range( 8, 11 ), 2 ); + + CAPTURE( i, j ); + SUCCEED(); + } + SECTION( "two" ) { + auto j = GENERATE( 3.141, 1.379 ); + CAPTURE( i, j ); + SUCCEED(); + } +} + +// This one generates the cross-product of two ranges. +// It's mostly here to demonstrate the performance which, at time of writing, +// leaves a lot to be desired. +TEST_CASE( "100x100 ints", "[.][approvals]" ) { + auto x = GENERATE( range( 0,100 ) ); + auto y = GENERATE( range( 200,300 ) ); + + CHECK( x < y ); +} + +// smaller version +TEST_CASE( "10x10 ints" ) { + auto x = GENERATE( range( 1,11 ) ); + auto y = GENERATE( range( 101, 111 ) ); + + CHECK( x < y ); +} + +// Some of the following tests use structured bindings for convenience and so are +// conditionally compiled using the de-facto (and soon to be formally) standard +// feature macros + +#ifdef __cpp_structured_bindings + +// One way to do pairs of values (actual/ expected?) +// For a simple case like this I'd recommend writing out a series of REQUIREs +// but it demonstrates a possible usage. +// Spelling out the pair like this is a bit verbose, so read on for better examples +// - the use of structured bindings here is an optional convenience +TEST_CASE( "strlen" ) { + auto [test_input, expected] = GENERATE( values>({ + {"one", 3}, + {"two", 3}, + {"three", 5}, + {"four", 4} + })); + + REQUIRE( test_input.size() == expected ); +} + +// A nicer way to do pairs (or more) of values - using the table generator. +// Note, you must specify the types up-front. +TEST_CASE( "strlen2" ) { + auto [test_input, expected] = GENERATE( table({ + {"one", 3}, + {"two", 3}, + {"three", 5}, + {"four", 4} + })); + + REQUIRE( test_input.size() == expected ); +} +#endif + +// An alternate way of doing data tables without structure bindings +// - I'd prefer to have the Data class within the test case but gcc 4.x doesn't seem to like it +struct Data { std::string str; size_t len; }; + +TEST_CASE( "strlen3" ) { + auto data = GENERATE( values({ + {"one", 3}, + {"two", 3}, + {"three", 5}, + {"four", 4} + })); + + REQUIRE( data.str.size() == data.len ); +} + +// A nod towards property-based testing - generate a random selection of numbers +// in a range and assert on global properties those numbers. +static auto square( int i ) -> int { return i*i; } + +TEST_CASE( "Random numbers in a range", "[.][approvals]" ) { + auto x = GENERATE( random( -10000, 10000 ) ); + CAPTURE( x ); + REQUIRE( square(x) >= 0 ); +} + +#ifdef __cpp_structured_bindings + +// Based on example from https://docs.cucumber.io/gherkin/reference/#scenario-outline +// (thanks to https://github.com/catchorg/Catch2/issues/850#issuecomment-399504851) + +// Note that GIVEN, WHEN, and THEN now forward onto DYNAMIC_SECTION instead of SECTION. +// DYNAMIC_SECTION takes its name as a stringstream-style expression, so can be formatted using +// variables in scope - such as the generated variables here. This reads quite nicely in the +// test name output (the full scenario description). + +auto eatCucumbers( int start, int eat ) -> int { return start-eat; } + +SCENARIO("Eating cucumbers") { + + auto [start, eat, left] = GENERATE( table ({ + { 12, 5, 7 }, + { 20, 5, 15 } + })); + + GIVEN( "there are " << start << " cucumbers" ) + WHEN( "I eat " << eat << " cucumbers" ) + THEN( "I should have " << left << " cucumbers" ) { + REQUIRE( eatCucumbers( start, eat ) == left ); + } +} +#endif