From 7c25dae9ea7b669430103d29cd262abb0842f4e5 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Tue, 12 Jun 2018 17:50:39 +0100 Subject: [PATCH] First attempt at data generator support The support is to be considered experimental, that is, the interfaces, the first party generators and helper functions can change or be removed at any point in time. Related to #850 --- include/catch.hpp | 1 + include/internal/catch_generators.cpp | 45 ++++ include/internal/catch_generators.hpp | 255 ++++++++++++++++++ include/internal/catch_interfaces_capture.h | 4 + .../catch_interfaces_generatortracker.h | 39 +++ include/internal/catch_run_context.cpp | 71 +++++ include/internal/catch_run_context.h | 3 + include/internal/catch_test_case_tracker.cpp | 15 +- include/internal/catch_test_case_tracker.h | 7 - projects/CMakeLists.txt | 5 + .../GeneratorsImpl.tests.cpp | 93 +++++++ .../SelfTest/UsageTests/Generators.tests.cpp | 134 +++++++++ 12 files changed, 656 insertions(+), 16 deletions(-) create mode 100644 include/internal/catch_generators.cpp create mode 100644 include/internal/catch_generators.hpp create mode 100644 include/internal/catch_interfaces_generatortracker.h create mode 100644 projects/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp create mode 100644 projects/SelfTest/UsageTests/Generators.tests.cpp 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