From 48f322697434292206db570e8fbe1e3ac8728d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Mon, 16 May 2022 16:34:06 +0200 Subject: [PATCH] Add support for retrieving generator's element as string --- src/CMakeLists.txt | 1 + src/catch2/generators/catch_generators.hpp | 5 ++ .../catch_interfaces_generatortracker.cpp | 32 +++++++ .../catch_interfaces_generatortracker.hpp | 33 +++++-- .../GeneratorsImpl.tests.cpp | 89 +++++++++++++++++++ 5 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 src/catch2/interfaces/catch_interfaces_generatortracker.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 705b31d0..f69d3166 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -184,6 +184,7 @@ set(IMPL_SOURCES ${SOURCES_DIR}/internal/catch_istream.cpp ${SOURCES_DIR}/generators/internal/catch_generators_combined_tu.cpp ${SOURCES_DIR}/interfaces/catch_interfaces_combined_tu.cpp + ${SOURCES_DIR}/interfaces/catch_interfaces_generatortracker.cpp ${SOURCES_DIR}/interfaces/catch_interfaces_reporter.cpp ${SOURCES_DIR}/internal/catch_list.cpp ${SOURCES_DIR}/matchers/catch_matchers_floating_point.cpp diff --git a/src/catch2/generators/catch_generators.hpp b/src/catch2/generators/catch_generators.hpp index 24af63d6..1de04e3b 100644 --- a/src/catch2/generators/catch_generators.hpp +++ b/src/catch2/generators/catch_generators.hpp @@ -8,6 +8,7 @@ #ifndef CATCH_GENERATORS_HPP_INCLUDED #define CATCH_GENERATORS_HPP_INCLUDED +#include #include #include #include @@ -32,6 +33,10 @@ namespace Detail { template class IGenerator : public GeneratorUntypedBase { + std::string stringifyImpl() const override { + return ::Catch::Detail::stringify( get() ); + } + public: ~IGenerator() override = default; IGenerator() = default; diff --git a/src/catch2/interfaces/catch_interfaces_generatortracker.cpp b/src/catch2/interfaces/catch_interfaces_generatortracker.cpp new file mode 100644 index 00000000..e8c67b54 --- /dev/null +++ b/src/catch2/interfaces/catch_interfaces_generatortracker.cpp @@ -0,0 +1,32 @@ + +// 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 +#include + +namespace Catch { + namespace Generators { + + bool GeneratorUntypedBase::countedNext() { + auto ret = next(); + if ( ret ) { + m_stringReprCache.clear(); + ++m_currentElementIndex; + } + return ret; + } + + StringRef GeneratorUntypedBase::currentElementAsString() const { + if ( m_stringReprCache.empty() ) { + m_stringReprCache = stringifyImpl(); + } + return m_stringReprCache; + } + + } // namespace Generators +} // namespace Catch diff --git a/src/catch2/interfaces/catch_interfaces_generatortracker.hpp b/src/catch2/interfaces/catch_interfaces_generatortracker.hpp index 2efc4a58..83e3d783 100644 --- a/src/catch2/interfaces/catch_interfaces_generatortracker.hpp +++ b/src/catch2/interfaces/catch_interfaces_generatortracker.hpp @@ -9,11 +9,18 @@ #define CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED #include +#include + +#include namespace Catch { namespace Generators { class GeneratorUntypedBase { + // Caches result from `toStringImpl`, assume that when it is an + // empty string, the cache is invalidated. + mutable std::string m_stringReprCache; + // Counts based on `next` returning true std::size_t m_currentElementIndex = 0; @@ -25,6 +32,9 @@ namespace Catch { */ virtual bool next() = 0; + //! Customization point for `currentElementAsString` + virtual std::string stringifyImpl() const = 0; + public: GeneratorUntypedBase() = default; // Generation of copy ops is deprecated (and Clang will complain) @@ -44,15 +54,24 @@ namespace Catch { * As with `next`, returns true iff the move succeeded and * the generator has new valid element to provide. */ - bool countedNext() { - auto ret = next(); - if ( ret ) { - ++m_currentElementIndex; - } - return ret; - } + bool countedNext(); std::size_t currentElementIndex() const { return m_currentElementIndex; } + + /** + * Returns generator's current element as user-friendly string. + * + * By default returns string equivalent to calling + * `Catch::Detail::stringify` on the current element, but generators + * can customize their implementation as needed. + * + * Not thread-safe due to internal caching. + * + * The returned ref is valid only until the generator instance + * is destructed, or it moves onto the next element, whichever + * comes first. + */ + StringRef currentElementAsString() const; }; using GeneratorBasePtr = Catch::Detail::unique_ptr; diff --git a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp index 42d80741..e0e06eb0 100644 --- a/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/GeneratorsImpl.tests.cpp @@ -424,3 +424,92 @@ TEST_CASE("Generators count returned elements", "[generators][approvals]") { REQUIRE_FALSE( generator.countedNext() ); REQUIRE( generator.currentElementIndex() == 2 ); } + +TEST_CASE( "Generators can stringify their elements", + "[generators][approvals]" ) { + auto generator = + Catch::Generators::FixedValuesGenerator( { 1, 2, 3 } ); + + REQUIRE( generator.currentElementAsString() == "1"_catch_sr ); + REQUIRE( generator.countedNext() ); + REQUIRE( generator.currentElementAsString() == "2"_catch_sr ); + REQUIRE( generator.countedNext() ); + REQUIRE( generator.currentElementAsString() == "3"_catch_sr ); +} + +namespace { + class CustomStringifyGenerator + : public Catch::Generators::IGenerator { + bool m_first = true; + + std::string stringifyImpl() const override { + return m_first ? "first" : "second"; + } + + bool next() override { + if ( m_first ) { + m_first = false; + return true; + } + return false; + } + + public: + bool const& get() const override; + }; + + // Avoids -Wweak-vtables + bool const& CustomStringifyGenerator::get() const { return m_first; } +} // namespace + +TEST_CASE( "Generators can override element stringification", + "[generators][approvals]" ) { + CustomStringifyGenerator generator; + REQUIRE( generator.currentElementAsString() == "first"_catch_sr ); + REQUIRE( generator.countedNext() ); + REQUIRE( generator.currentElementAsString() == "second"_catch_sr ); +} + +namespace { + class StringifyCountingGenerator + : public Catch::Generators::IGenerator { + bool m_first = true; + mutable size_t m_stringificationCalls = 0; + + std::string stringifyImpl() const override { + ++m_stringificationCalls; + return m_first ? "first" : "second"; + } + + bool next() override { + if ( m_first ) { + m_first = false; + return true; + } + return false; + } + + public: + + bool const& get() const override; + size_t stringificationCalls() const { return m_stringificationCalls; } + }; + + // Avoids -Wweak-vtables + bool const& StringifyCountingGenerator::get() const { return m_first; } + +} // namespace + +TEST_CASE( "Generator element stringification is cached", + "[generators][approvals]" ) { + StringifyCountingGenerator generator; + REQUIRE( generator.currentElementAsString() == "first"_catch_sr ); + REQUIRE( generator.currentElementAsString() == "first"_catch_sr ); + REQUIRE( generator.currentElementAsString() == "first"_catch_sr ); + REQUIRE( generator.currentElementAsString() == "first"_catch_sr ); + REQUIRE( generator.currentElementAsString() == "first"_catch_sr ); + + REQUIRE( generator.stringificationCalls() == 1 ); +} + +// caching?