From 2a7d33a38c1adc12092a0305aa3ccdb5f1db5532 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Fri, 10 Jun 2016 19:37:27 +0100 Subject: [PATCH] First cut of new, non-owning, StringRef class, with no dependencies in header --- include/internal/catch_impl.hpp | 1 + include/internal/catch_stringref.h | 82 +++++++++++ include/internal/catch_stringref.hpp | 112 +++++++++++++++ projects/SelfTest/StringTests.cpp | 131 ++++++++++++++++++ .../CatchSelfTest.xcodeproj/project.pbxproj | 13 +- 5 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 include/internal/catch_stringref.h create mode 100644 include/internal/catch_stringref.hpp create mode 100644 projects/SelfTest/StringTests.cpp diff --git a/include/internal/catch_impl.hpp b/include/internal/catch_impl.hpp index 5c6a1a2a..1bc102b4 100644 --- a/include/internal/catch_impl.hpp +++ b/include/internal/catch_impl.hpp @@ -16,6 +16,7 @@ #pragma clang diagnostic ignored "-Wweak-vtables" #endif +#include "catch_stringref.hpp" #include "../catch_session.hpp" #include "catch_registry_hub.hpp" #include "catch_notimplemented_exception.hpp" diff --git a/include/internal/catch_stringref.h b/include/internal/catch_stringref.h new file mode 100644 index 00000000..2f8a391b --- /dev/null +++ b/include/internal/catch_stringref.h @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Two Blue Cubes Ltd. All rights reserved. + * + * 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_STRINGREF_H_INCLUDED +#define TWOBLUECUBES_CATCH_STRINGREF_H_INCLUDED + +#include "internal/catch_suppress_warnings.h" + +namespace Catch { + + // A non-owning string class (similar to the forthcoming std::string_view) + // Note that, because a StringRef may be a substring of another string, + // it may not be null terminated. c_str() must return a null terminated + // string, however, and so the StringRef will internally take ownership + // (taking a copy), if necessary. This internal mutable is not externally + // visible - but does mean StringRefs should not be shared between + // threads. + class StringRef { + protected: + enum class Ownership { + FullStringRef, + SubStringRef, + FullStringOwned + }; + + using size_type = unsigned long; + + char const* m_data; + size_type m_size; + Ownership m_ownership; + + static StringRef s_emptyStringRef; + + void takeOwnership(); + void deleteIfOwned(); + + // Access for testing purposes only //////////// + friend auto rawCharData( StringRef const& stringRef ) -> char const*; + friend auto isOwned( StringRef const& stringRef ) -> bool; + friend auto isSubstring( StringRef const& stringRef ) -> bool; + //////////////////////////////////////////////// + + public: // construction/ assignment + StringRef(); + StringRef( StringRef const& other ); + StringRef( StringRef&& other ); + StringRef( char const* rawChars ); + StringRef( char const* rawChars, size_type size ); + ~StringRef(); + + auto operator = ( StringRef const& other ) -> StringRef&; + auto operator = ( StringRef&& other ) -> StringRef&; + + public: // operators + auto operator == ( StringRef const& other ) const -> bool; + auto operator != ( StringRef const& other ) const -> bool; + + auto operator[] ( size_type index ) const -> char { + return m_data[index]; + } + + public: // named queries + auto empty() const -> bool { + return m_size == 0; + } + auto size() const -> size_type { + return m_size; + } + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const -> StringRef; + }; + +} // namespace Catch + +#include "internal/catch_reenable_warnings.h" + +#endif // TWOBLUECUBES_CATCH_STRINGREF_H_INCLUDED diff --git a/include/internal/catch_stringref.hpp b/include/internal/catch_stringref.hpp new file mode 100644 index 00000000..4e794e0f --- /dev/null +++ b/include/internal/catch_stringref.hpp @@ -0,0 +1,112 @@ +#include "catch_stringref.h" + +#include + +namespace Catch { + + StringRef StringRef::s_emptyStringRef = ""; + + StringRef::StringRef() + : StringRef( s_emptyStringRef ) + {} + + StringRef::StringRef( StringRef const& other ) + : m_data( other.m_data ), + m_size( other.m_size ), + m_ownership( other.m_ownership ) + { + if( m_ownership == Ownership::FullStringOwned ) + m_ownership = Ownership::FullStringRef; + } + + StringRef::StringRef( StringRef&& other ) + : m_data( other.m_data ), + m_size( other.m_size ), + m_ownership( other.m_ownership ) + { + if( m_ownership == Ownership::FullStringOwned ) + other.m_ownership = Ownership::FullStringRef; + } + + StringRef::StringRef( char const* rawChars ) + : m_data( rawChars ), + m_size( std::strlen( rawChars ) ), + m_ownership( Ownership::FullStringRef ) + {} + StringRef::StringRef( char const* rawChars, size_type size ) + : m_data( rawChars ), + m_size( size ), + m_ownership( Ownership::SubStringRef ) + { + size_type rawSize = std::strlen( rawChars ); + if( rawSize < size ) + size = rawSize; + if( rawSize == size ) + m_ownership = Ownership::FullStringRef; + } + + StringRef::~StringRef() { + deleteIfOwned(); + } + + void StringRef::deleteIfOwned() { + if( m_ownership == Ownership::FullStringOwned ) + delete m_data; + } + + auto StringRef::operator = ( StringRef const& other ) -> StringRef& { + if( &other == this ) + return *this; + deleteIfOwned(); + m_data = other.m_data; + m_size = other.m_size; + if( other.m_ownership == Ownership::FullStringOwned ) + m_ownership = Ownership::FullStringRef; + else + m_ownership = other.m_ownership; + return *this; + } + + auto StringRef::operator = ( StringRef&& other ) -> StringRef& { + if( &other == this ) + return *this; + deleteIfOwned(); + m_data = other.m_data; + m_size = other.m_size; + m_ownership = other.m_ownership; + if( other.m_ownership == Ownership::FullStringOwned ) + other.m_ownership = Ownership::FullStringRef; + return *this; + } + + auto StringRef::c_str() const -> char const* { + if( m_ownership == Ownership::SubStringRef ) + const_cast( this )->takeOwnership(); + return m_data; + } + + void StringRef::takeOwnership() { + if( m_ownership != Ownership::FullStringOwned ) { + char* data = new char[m_size+1]; + std::memcpy( data, m_data, m_size ); + data[m_size] = '\0'; + m_data = data; + m_ownership = Ownership::FullStringOwned; + } + } + auto StringRef::substr( size_type start, size_type size ) const -> StringRef { + if( start < m_size ) + return StringRef( m_data+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const -> bool { + return size() == other.size() && + (std::strncmp( m_data, other.m_data, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const -> bool { + return !operator==( other ); + } + + +} // namespace Catch diff --git a/projects/SelfTest/StringTests.cpp b/projects/SelfTest/StringTests.cpp new file mode 100644 index 00000000..5be2cb05 --- /dev/null +++ b/projects/SelfTest/StringTests.cpp @@ -0,0 +1,131 @@ +#include "internal/catch_stringref.h" + +#include "internal/catch_suppress_warnings.h" + +// !TBD always owning string implementation +namespace Catch { + + class String { + public: + String() {} + }; +} + +namespace Catch { + + + +} + +#include + +namespace Catch { + + // Implementation of test accessors + auto rawCharData( StringRef const& stringRef ) -> char const* { + return stringRef.m_data; + } + auto isOwned( StringRef const& stringRef ) -> bool { + return stringRef.m_ownership == StringRef::Ownership::FullStringOwned; + } + auto isSubstring( StringRef const& stringRef ) -> bool { + return stringRef.m_ownership == StringRef::Ownership::SubStringRef; + } + + // Allow StringRefs to be converted to std::strings for printing + // - this will become redundant when toString is reimplemented in terms + // of String/StringRef + inline auto toString( StringRef const& stringRef ) -> std::string { + return std::string( rawCharData( stringRef ), stringRef.size() ); + } +} + +#include "catch.hpp" + +TEST_CASE( "StringRef" ) { + + using Catch::StringRef; + + SECTION( "Empty string" ) { + StringRef empty; + REQUIRE( empty.empty() ); + REQUIRE( empty.size() == 0 ); + REQUIRE( std::strcmp( empty.c_str(), "" ) == 0 ); + } + + SECTION( "From string literal" ) { + StringRef s = "hello"; + REQUIRE( s.empty() == false ); + REQUIRE( s.size() == 5 ); + REQUIRE( isSubstring( s ) == false ); + + auto rawChars = rawCharData( s ); + REQUIRE( std::strcmp( rawChars, "hello" ) == 0 ); + + SECTION( "c_str() does not cause copy" ) { + REQUIRE( isOwned( s ) == false ); + + REQUIRE( s.c_str() == rawChars ); + + REQUIRE( isOwned( s ) == false ); + } + } + SECTION( "From sub-string" ) { + StringRef original = StringRef( "original string" ).substr(0, 8); + REQUIRE( original == "original" ); + REQUIRE( isSubstring( original ) ); + REQUIRE( isOwned( original ) == false ); + + original.c_str(); // Forces it to take ownership + + REQUIRE( isSubstring( original ) == false ); + REQUIRE( isOwned( original ) ); + + } + + + SECTION( "Substrings" ) { + StringRef s = "hello world!"; + StringRef ss = s.substr(0, 5); + + SECTION( "zero-based substring" ) { + REQUIRE( ss.empty() == false ); + REQUIRE( ss.size() == 5 ); + REQUIRE( std::strcmp( ss.c_str(), "hello" ) == 0 ); + REQUIRE( ss == "hello" ); + } + SECTION( "c_str() causes copy" ) { + REQUIRE( isSubstring( ss ) ); + REQUIRE( isOwned( ss ) == false ); + + auto rawChars = rawCharData( ss ); + REQUIRE( rawChars == rawCharData( s ) ); // same pointer value + REQUIRE( ss.c_str() != rawChars ); + + REQUIRE( isSubstring( ss ) == false ); + REQUIRE( isOwned( ss ) ); + + REQUIRE( rawCharData( ss ) != rawCharData( s ) ); // different pointer value + } + + SECTION( "non-zero-based substring") { + ss = s.substr( 6, 6 ); + REQUIRE( ss.size() == 6 ); + REQUIRE( std::strcmp( ss.c_str(), "world!" ) == 0 ); + } + + SECTION( "Pointer values of full refs should match" ) { + StringRef s2 = s; + REQUIRE( s.c_str() == s2.c_str() ); + } + + SECTION( "Pointer values of substring refs should not match" ) { + REQUIRE( s.c_str() != ss.c_str() ); + } + } + + SECTION( "Comparisons" ) { + REQUIRE( StringRef("hello") == StringRef("hello") ); + REQUIRE( StringRef("hello") != StringRef("cello") ); + } +} diff --git a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj b/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj index 3eeaac1c..3a1cd9b7 100644 --- a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj +++ b/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 2691574C1A532A280054F1ED /* ToStringTuple.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2691574B1A532A280054F1ED /* ToStringTuple.cpp */; }; 2694A1FD16A0000E004816E3 /* catch_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2694A1FB16A0000E004816E3 /* catch_text.cpp */; }; 269E42321C04E21300133E05 /* ThreadedTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 269E42311C04E21300133E05 /* ThreadedTests.cpp */; }; + 26ADDC0A1D077DB3008F7108 /* StringTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26ADDC091D077DB3008F7108 /* StringTests.cpp */; }; 26E1B7D319213BC900812682 /* CmdLineTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26E1B7D119213BC900812682 /* CmdLineTests.cpp */; }; 4A45DA2416161EF9004F8D6B /* catch_console_colour.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A45DA2316161EF9004F8D6B /* catch_console_colour.cpp */; }; 4A45DA2716161F1F004F8D6B /* catch_ptr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4A45DA2616161F1F004F8D6B /* catch_ptr.cpp */; }; @@ -106,6 +107,9 @@ 269831E719121CA500BB0CE0 /* catch_reporter_compact.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_reporter_compact.hpp; sourceTree = ""; }; 269E42311C04E21300133E05 /* ThreadedTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ThreadedTests.cpp; path = ../../../SelfTest/ThreadedTests.cpp; sourceTree = ""; }; 269E42331C04E33D00133E05 /* catch_thread_context.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_thread_context.hpp; sourceTree = ""; }; + 26ADDC091D077DB3008F7108 /* StringTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StringTests.cpp; path = ../../../SelfTest/StringTests.cpp; sourceTree = ""; }; + 26ADDC0B1D0B394D008F7108 /* catch_stringref.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = catch_stringref.h; sourceTree = ""; }; + 26ADDC0C1D0B3969008F7108 /* catch_stringref.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch_stringref.hpp; sourceTree = ""; }; 26AEAF1617BEA18E009E32C9 /* catch_platform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_platform.h; sourceTree = ""; }; 26DACF2F17206D3400A21326 /* catch_text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = catch_text.h; sourceTree = ""; }; 26DFD3B11B53F84700FD6F16 /* catch_wildcard_pattern.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catch_wildcard_pattern.hpp; sourceTree = ""; }; @@ -208,6 +212,7 @@ 26059AF11BD4B94C003D575C /* PartTrackerTests.cpp */, 26E1B7D119213BC900812682 /* CmdLineTests.cpp */, 26711C8D195D465C0033EDA2 /* TagAliasTests.cpp */, + 26ADDC091D077DB3008F7108 /* StringTests.cpp */, ); name = "Introspective Tests"; sourceTree = ""; @@ -361,6 +366,7 @@ 2627F7061935B55F009BCE2D /* catch_result_builder.hpp */, 26711C92195D48F60033EDA2 /* catch_tag_alias_registry.hpp */, 269E42331C04E33D00133E05 /* catch_thread_context.hpp */, + 26ADDC0C1D0B3969008F7108 /* catch_stringref.hpp */, ); name = impl; sourceTree = ""; @@ -460,6 +466,7 @@ 2656C227192A78410040DB02 /* catch_reenable_warnings.h */, 263F7A4519A66608009474C2 /* catch_fatal_condition.hpp */, 26DFD3B11B53F84700FD6F16 /* catch_wildcard_pattern.hpp */, + 26ADDC0B1D0B394D008F7108 /* catch_stringref.h */, ); name = Infrastructure; sourceTree = ""; @@ -541,6 +548,7 @@ 4AEE032316142FC70071E950 /* catch_debugger.cpp in Sources */, 269E42321C04E21300133E05 /* ThreadedTests.cpp in Sources */, 4AEE032516142FF10071E950 /* catch_stream.cpp in Sources */, + 26ADDC0A1D077DB3008F7108 /* StringTests.cpp in Sources */, 4AEE0328161434FD0071E950 /* catch_xmlwriter.cpp in Sources */, 4A45DA2416161EF9004F8D6B /* catch_console_colour.cpp in Sources */, 4A45DA2716161F1F004F8D6B /* catch_ptr.cpp in Sources */, @@ -548,6 +556,7 @@ 2656C2211925E7330040DB02 /* catch_test_spec.cpp in Sources */, 4A45DA2916161F3D004F8D6B /* catch_streambuf.cpp in Sources */, 4A45DA2B16161F79004F8D6B /* catch_interfaces_registry_hub.cpp in Sources */, + 26ADDC0D1D0B3969008F7108 /* catch_stringref.hpp in Sources */, 4A45DA2D16161FA2004F8D6B /* catch_interfaces_capture.cpp in Sources */, 4A45DA3116161FFC004F8D6B /* catch_interfaces_reporter.cpp in Sources */, 4A45DA3316162047004F8D6B /* catch_interfaces_exception.cpp in Sources */, @@ -572,7 +581,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; - CLANG_WARN_CXX0X_EXTENSIONS = YES; + CLANG_WARN_CXX0X_EXTENSIONS = NO; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; @@ -625,7 +634,7 @@ buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; CLANG_CXX_LIBRARY = "libc++"; - CLANG_WARN_CXX0X_EXTENSIONS = YES; + CLANG_WARN_CXX0X_EXTENSIONS = NO; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;