From 78e799443531a84c5f337109d462af53249ed49c Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Thu, 29 Jun 2017 11:18:14 +0100 Subject: [PATCH] Added string classes --- CMakeLists.txt | 13 ++ include/internal/catch_impl.hpp | 9 ++ include/internal/catch_string.cpp | 83 ++++++++++++ include/internal/catch_string.h | 48 +++++++ include/internal/catch_stringbuilder.cpp | 111 ++++++++++++++++ include/internal/catch_stringbuilder.h | 72 +++++++++++ include/internal/catch_stringdata.cpp | 49 +++++++ include/internal/catch_stringdata.h | 52 ++++++++ include/internal/catch_stringref.cpp | 140 ++++++++++++++++++++ include/internal/catch_stringref.h | 84 ++++++++++++ projects/SelfTest/String.tests.cpp | 20 +++ projects/SelfTest/StringBuilder.tests.cpp | 76 +++++++++++ projects/SelfTest/StringRef.tests.cpp | 151 ++++++++++++++++++++++ 13 files changed, 908 insertions(+) create mode 100644 include/internal/catch_string.cpp create mode 100644 include/internal/catch_string.h create mode 100644 include/internal/catch_stringbuilder.cpp create mode 100644 include/internal/catch_stringbuilder.h create mode 100644 include/internal/catch_stringdata.cpp create mode 100644 include/internal/catch_stringdata.h create mode 100644 include/internal/catch_stringref.cpp create mode 100644 include/internal/catch_stringref.h create mode 100644 projects/SelfTest/String.tests.cpp create mode 100644 projects/SelfTest/StringBuilder.tests.cpp create mode 100644 projects/SelfTest/StringRef.tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index be37e4d7..ed863deb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/TrickyTests.cpp ${SELF_TEST_DIR}/VariadicMacrosTests.cpp ${SELF_TEST_DIR}/MatchersTests.cpp + ${SELF_TEST_DIR}/String.tests.cpp + ${SELF_TEST_DIR}/StringBuilder.tests.cpp + ${SELF_TEST_DIR}/StringRef.tests.cpp ) CheckFileList(TEST_SOURCES ${SELF_TEST_DIR}) @@ -181,6 +184,14 @@ set(INTERNAL_HEADERS ${HEADER_DIR}/internal/catch_stream.h ${HEADER_DIR}/internal/catch_stream.hpp ${HEADER_DIR}/internal/catch_streambuf.h + ${HEADER_DIR}/internal/catch_string.cpp + ${HEADER_DIR}/internal/catch_string.h + ${HEADER_DIR}/internal/catch_stringbuilder.cpp + ${HEADER_DIR}/internal/catch_stringbuilder.h + ${HEADER_DIR}/internal/catch_stringdata.cpp + ${HEADER_DIR}/internal/catch_stringdata.h + ${HEADER_DIR}/internal/catch_stringref.cpp + ${HEADER_DIR}/internal/catch_stringref.h ${HEADER_DIR}/internal/catch_suppress_warnings.h ${HEADER_DIR}/internal/catch_tag_alias.h ${HEADER_DIR}/internal/catch_tag_alias_registry.h @@ -243,6 +254,8 @@ SOURCE_GROUP("Benchmarks" FILES ${BENCH_SOURCES}) # configure the executable include_directories(${HEADER_DIR}) +add_definitions( -DCATCH_CONFIG_FULL_PROJECT ) + # Projects consuming Catch via ExternalProject_Add might want to use install step # without building all of our selftests. if (NOT NO_SELFTEST) diff --git a/include/internal/catch_impl.hpp b/include/internal/catch_impl.hpp index 4843dad6..2306af7f 100644 --- a/include/internal/catch_impl.hpp +++ b/include/internal/catch_impl.hpp @@ -37,6 +37,15 @@ #include "catch_matchers_string.hpp" #include "catch_startup_exception_registry.hpp" +// These files are not included in the full (not single include) project +// as they are compiled as proper cpp files +#ifndef CATCH_CONFIG_FULL_PROJECT +# include "catch_stringref.cpp" +# include "catch_string.cpp" +# include "catch_stringbuilder.cpp" +# include "catch_stringdata.cpp" +#endif + #include "../reporters/catch_reporter_multi.hpp" #include "../reporters/catch_reporter_xml.hpp" #include "../reporters/catch_reporter_junit.hpp" diff --git a/include/internal/catch_string.cpp b/include/internal/catch_string.cpp new file mode 100644 index 00000000..f7cc53a5 --- /dev/null +++ b/include/internal/catch_string.cpp @@ -0,0 +1,83 @@ +/* + * 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) + */ + +#include "catch_string.h" +#include "catch_stringref.h" +#include "catch_stringbuilder.h" + +#include "catch_stringdata.h" + +#include + +namespace Catch { + + String::String() + : m_data( StringData::getEmpty() ) + {} + + String::String( StringRef const& stringRef ) + : m_data( StringData::create( stringRef ) ) + {} + + String::String( char const* rawString ) + : String( StringRef( rawString ) ) + {} + + String::String( String const& other ) + : m_data( other.m_data ) + { + m_data->addRef(); + } + String::String( String&& other ) + : m_data( other.m_data ) + { + other.m_data = StringData::getEmpty(); + } + String::String( StringBuilder&& stringBuf ) + : m_data( stringBuf.m_data ) + { + // const_cast is ok here because we are taking ownership + const_cast( m_data )->size = stringBuf.size(); + stringBuf.m_data = StringData::getEmpty(); + stringBuf.m_size = 0; + } + + + String::~String() noexcept { + m_data->release(); + } + + auto String::operator = ( String const& other ) -> String& { + m_data = other.m_data; + m_data->addRef(); + return *this; + } + + + auto String::empty() const noexcept -> bool { + return m_data->size == 0; + } + auto String::size() const noexcept -> size_type { + return m_data->size; + } + auto String::c_str() const noexcept -> char const* { + return m_data->chars; + } + + auto String::operator == ( StringRef const& other ) const noexcept -> bool { + return other == StringRef( *this ); + } + auto String::operator == ( char const* other ) const noexcept -> bool { + return StringRef( other ) == StringRef( *this ); + } + + std::ostream& operator << ( std::ostream& os, String const& str ) { + os << str.c_str(); + return os; + } + +} // namespace Catch diff --git a/include/internal/catch_string.h b/include/internal/catch_string.h new file mode 100644 index 00000000..b9d310c9 --- /dev/null +++ b/include/internal/catch_string.h @@ -0,0 +1,48 @@ +/* + * 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 CATCH_STRING_H_INCLUDED +#define CATCH_STRING_H_INCLUDED + +namespace Catch { + + class StringData; + class StringRef; + class StringBuilder; + + /// An owning, ref-counted, immutable string type. + /// The ref count (not visible here as it is defined in StringData) is atomic + /// so instances should be safe to share across threads + class String { + friend class StringRef; + friend class StringBuilder; + + StringData const* m_data = nullptr; + public: + using size_type = unsigned long; + + String(); + String( StringRef const& stringRef ); + String( char const* rawString ); + String( String const& other ); + String( String&& other ); + String( StringBuilder&& stringBuf ); + + ~String() noexcept; + + auto operator = ( String const& other ) -> String&; + + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator == ( char const* other ) const noexcept -> bool; + + auto empty() const noexcept -> bool; + auto size() const noexcept -> size_type; + auto c_str() const noexcept -> char const*; + }; + +} // namespace Catch + +#endif // CATCH_STRING_H_INCLUDED diff --git a/include/internal/catch_stringbuilder.cpp b/include/internal/catch_stringbuilder.cpp new file mode 100644 index 00000000..80ba1bbd --- /dev/null +++ b/include/internal/catch_stringbuilder.cpp @@ -0,0 +1,111 @@ +/* + * 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) + */ + +#include "catch_stringbuilder.h" +#include "catch_stringref.h" +#include "catch_stringdata.h" +#include "catch_string.h" + +#include +#include +#include +#include + +namespace Catch { + + static const StringBuilder::size_type s_minimumCapacity = 32; + + StringBuilder::StringBuilder() + : m_data( StringData::getEmpty() ) + {} + + StringBuilder::StringBuilder( size_type initialCapacity ) + : m_size( 0 ), + m_data( StringData::create( StringRef(), initialCapacity ) ) + {} + StringBuilder::StringBuilder( StringRef const& str, size_type initialCapacity ) + : m_size( str.size() ), + m_data( StringData::create( str, initialCapacity ) ) + {} + StringBuilder::StringBuilder( StringBuilder const& other, size_type initialCapacity ) + : StringBuilder( StringRef( other.m_data->chars, other.m_size ), initialCapacity ) + {} + StringBuilder::StringBuilder( StringBuilder&& other ) noexcept + : StringBuilder() + { + swap( other ); + } + StringBuilder::StringBuilder( String&& str ) + : m_size( str.size() ), + m_data( StringData::getEmpty() ) + { + if( str.m_data->isUniquelyOwned() ) + { + std::swap( m_data, const_cast( str.m_data ) ); + } + else + { + size_type initialCapacity = std::min( s_minimumCapacity, m_size ); + m_data = StringData::create( str, initialCapacity ); + } + } + StringBuilder::StringBuilder( String const& other ) + : StringBuilder( StringRef( other ), std::min( s_minimumCapacity, other.size() ) ) + {} + + StringBuilder::~StringBuilder() noexcept { + m_data->release(); + } + + auto StringBuilder::size() const noexcept -> size_type { + return m_size; + } + + void StringBuilder::swap( StringBuilder& other ) noexcept { + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + void StringBuilder::reserve( size_type minimumCapacity ) { + if( minimumCapacity > capacity() ) { + StringBuilder temp( *this, minimumCapacity ); + swap( temp ); + } + } + void StringBuilder::reserveExponential( size_type minimumCapacity ) { + if( minimumCapacity > capacity() ) { + size_type candidateCapacity = capacity() < s_minimumCapacity ? s_minimumCapacity : capacity()*2; + while( candidateCapacity < minimumCapacity ) + candidateCapacity = candidateCapacity * 3/2; // grow factor of 1.5 + StringBuilder temp( *this, candidateCapacity ); + swap( temp ); + } + } + auto StringBuilder::capacity() const noexcept -> size_type { + return m_data->size; + } + void StringBuilder::writeTo( size_type index, StringRef const& str ) { + assert( index + str.size() < capacity() ); + if( str.size() > 0 ) + std::memcpy( m_data->chars+index, str.data(), str.size() ); + } + void StringBuilder::append( StringRef const& str ) { + reserveExponential( m_size + str.size() + 1 ); + writeTo( m_size, str ); + m_size += str.size(); + m_data->chars[m_size] = '\0'; + } + + auto operator << ( StringBuilder& sb, StringRef sr ) -> StringBuilder& { + sb.append( sr ); + return sb; + } + auto operator << ( StringBuilder&& sb, StringRef sr ) -> StringBuilder&& { + sb.append( sr ); + return std::move( sb ); + } + +} // namespace Catch diff --git a/include/internal/catch_stringbuilder.h b/include/internal/catch_stringbuilder.h new file mode 100644 index 00000000..c3d99d81 --- /dev/null +++ b/include/internal/catch_stringbuilder.h @@ -0,0 +1,72 @@ +/* + * 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 CATCH_STRINGBUILDER_H_INCLUDED +#define CATCH_STRINGBUILDER_H_INCLUDED + +#include "catch_stringref.h" + +namespace Catch { + + class String; + class StringData; + + /// A mutable container for string data + /// Use to build up strings before transferring to an immutable String. + /// Construct the String using the rvalue reference constructor (which + /// will usually involve std::move-ing the StringBuilder). This will transfer + /// The underlying buffer without any extra allocations or ref counts. + class StringBuilder { + friend class String; + public: + using size_type = unsigned long; + + StringBuilder(); + StringBuilder( size_type initialCapacity ); + StringBuilder( StringRef const& str, size_type initialCapacity ); + StringBuilder( StringBuilder const& other, size_type initialCapacity ); + StringBuilder( StringBuilder&& other ) noexcept; + StringBuilder( String&& other ); + StringBuilder( String const& other ); + ~StringBuilder() noexcept; + + void swap( StringBuilder& other ) noexcept; + + auto size() const noexcept -> size_type; + auto capacity() const noexcept -> size_type; + + /// Grows the buffer to exactly the capacity requested, or + /// does nothing if it is already at least as big + void reserve(size_type capacity); + + /// Grows the buffer exponentially (from a baseline of 32 bytes) + /// until it is at least as large as the requested capacity - + /// or does nothing if already large enough + void reserveExponential(size_type capacity); + + /// Writes the string at the current insertion point then moves + /// the insertion point forward by the string length. + /// If the buffer needs to grow to accomodate the string it does so + /// using the exponential strategy + void append( StringRef const& str ); + + friend auto operator << ( StringBuilder& sb, StringRef sr ) -> StringBuilder&; + friend auto operator << ( StringBuilder&& sb, StringRef sr ) -> StringBuilder&&; + + /// Writes the contents of the string ref into the buffer at + /// the indexed location. + /// The bounds are not checked! Use append() to just add to the + /// end of the buffer, extending it if the capacity is not enough. + void writeTo( size_type index, StringRef const& str ); + + private: + size_type m_size = 0; + StringData* m_data; + }; + +} // namespace Catch + +#endif // CATCH_STRINGBUILDER_H_INCLUDED diff --git a/include/internal/catch_stringdata.cpp b/include/internal/catch_stringdata.cpp new file mode 100644 index 00000000..9f1625a8 --- /dev/null +++ b/include/internal/catch_stringdata.cpp @@ -0,0 +1,49 @@ +/* + * 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) + */ + + +#include "catch_stringdata.h" +#include "catch_stringref.h" + +#include +#include +#include + +namespace Catch { + + auto StringData::getEmpty() -> StringData* { + static StringData s_empty( 0 ); + return &s_empty; + } + auto StringData::create( StringRef const& stringRef ) -> StringData* { + return create( stringRef, stringRef.size() ); + } + auto StringData::create( StringRef const& stringRef, unsigned long capacity ) -> StringData* { + if( capacity == 0 ) { + return getEmpty(); + } + else { + assert( stringRef.size() <= capacity ); + auto bufferLen = sizeof(StringData)+capacity; + void* buffer = new char[bufferLen]; + + return new(buffer) StringData( stringRef, capacity ); + } + } + StringData::StringData( unsigned int initialRef ) + : m_refs( initialRef ), + size( 0 ) + {} + StringData::StringData( StringRef const& stringRef, unsigned long capacity ) + : m_refs( 1 ), + size( capacity) + { + std::memcpy( chars, stringRef.data(), stringRef.size() ); + chars[stringRef.size() ] = 0; + } + +} // namespace Catch \ No newline at end of file diff --git a/include/internal/catch_stringdata.h b/include/internal/catch_stringdata.h new file mode 100644 index 00000000..5ffafad7 --- /dev/null +++ b/include/internal/catch_stringdata.h @@ -0,0 +1,52 @@ +/* + * 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 CATCH_STRINGDATA_H_INCLUDED +#define CATCH_STRINGDATA_H_INCLUDED + +#include + +namespace Catch { + + class StringRef; + + class StringData { + mutable std::atomic m_refs; + public: + unsigned int size; + union { + char chars[1]; + }; + + auto isUniquelyOwned() const noexcept -> bool { + return m_refs == 1; + } + static auto getEmpty() -> StringData*; + static auto create( StringRef const& stringRef ) -> StringData*; + static auto create( StringRef const& stringRef, unsigned long capacity ) -> StringData*; + + void addRef() const noexcept { + if( m_refs > 0 ) + m_refs++; + } + void release() const noexcept { + unsigned int refs = m_refs; + if( refs > 1 ) + m_refs--; + else if( refs == 1 ) + delete[] reinterpret_cast( this ); + } + private: + StringData( unsigned int initialRef = 1 ); + StringData( StringRef const& stringRef, unsigned long capacity ); + + StringData( StringData const& ) = delete; + StringData& operator=( StringData const& ) = delete; + }; + +} // namespace Catch + +#endif // CATCH_STRINGDATA_H_INCLUDED diff --git a/include/internal/catch_stringref.cpp b/include/internal/catch_stringref.cpp new file mode 100644 index 00000000..d577d3af --- /dev/null +++ b/include/internal/catch_stringref.cpp @@ -0,0 +1,140 @@ +/* + * 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) + */ + +#include "catch_stringref.h" +#include "catch_stringbuilder.h" +#include "catch_string.h" +#include "catch_stringdata.h" + +#include +#include + +namespace Catch { + + StringRef StringRef::s_emptyStringRef = ""; + + StringRef::StringRef() + : StringRef( s_emptyStringRef ) + {} + + StringRef::StringRef( StringRef const& other ) + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + if( m_data ) + m_data->addRef(); + } + + StringRef::StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef::StringRef( char const* rawChars ) + : m_start( rawChars ), + m_size( std::strlen( rawChars ) ) + {} + + StringRef::StringRef( char const* rawChars, size_type size ) + : m_start( rawChars ), + m_size( size ) + { + size_type rawSize = rawChars == nullptr ? 0 : std::strlen( rawChars ); + if( rawSize < size ) + size = rawSize; + } + + StringRef::StringRef( String const& other ) + : m_start( other.c_str() ), + m_size( other.size() ), + m_data( nullptr ) + {} + + StringRef::StringRef( String&& str ) noexcept + : m_start( str.c_str() ), + m_size( str.size() ), + m_data( str.m_data ) + { + str.m_data = StringData::getEmpty(); + } + + StringRef::~StringRef() noexcept { + if( isOwned() ) + m_data->release(); + } + + auto StringRef::operator = ( StringRef other ) -> StringRef& { + swap( other ); + return *this; + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast( this )->takeOwnership(); + return m_start; + } + auto StringRef::data() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + StringRef temp = String( *this ); + swap( temp ); + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> String { + StringBuilder buf; + buf.reserve( lhs.size() + rhs.size() ); + buf.append( lhs ); + buf.append( rhs ); + return String( std::move( buf ) ); + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> String { + return lhs + StringRef( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> String { + return StringRef( lhs ) + rhs; + } + + std::ostream& operator << ( std::ostream& os, StringRef const& str ) { + return os << str.c_str(); + } + +} // namespace Catch diff --git a/include/internal/catch_stringref.h b/include/internal/catch_stringref.h new file mode 100644 index 00000000..a0d8200b --- /dev/null +++ b/include/internal/catch_stringref.h @@ -0,0 +1,84 @@ +/* + * 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 CATCH_STRINGREF_H_INCLUDED +#define CATCH_STRINGREF_H_INCLUDED + +namespace Catch { + + class String; + class StringData; + + /// 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. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + friend struct StringRefTestAccess; + friend class StringData; + friend class StringBuilder; + + using size_type = unsigned long; + + char const* m_start; + size_type m_size; + + StringData const* m_data = nullptr; + + static StringRef s_emptyStringRef; + + void takeOwnership(); + + public: // construction/ assignment + StringRef(); + StringRef( StringRef const& other ); + StringRef( StringRef&& other ) noexcept; + StringRef( char const* rawChars ); + StringRef( char const* rawChars, size_type size ); + StringRef( String const& other ); + StringRef( String&& other ) noexcept; + ~StringRef() noexcept; + + auto operator = ( StringRef other ) -> StringRef&; + + void swap( StringRef& other ) noexcept; + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; + + auto operator[] ( size_type index ) const noexcept -> char { + return m_start[index]; + } + + public: // named queries + auto empty() const noexcept -> bool { + return m_size == 0; + } + auto size() const noexcept -> size_type { + return m_size; + } + auto c_str() const -> char const*; + + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; + + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + auto data() const noexcept -> char const*; + }; + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> String; + auto operator + ( StringRef const& lhs, char const* rhs ) -> String; + auto operator + ( char const* lhs, StringRef const& rhs ) -> String; + +} // namespace Catch + +#endif // CATCH_STRINGREF_H_INCLUDED diff --git a/projects/SelfTest/String.tests.cpp b/projects/SelfTest/String.tests.cpp new file mode 100644 index 00000000..ccd90964 --- /dev/null +++ b/projects/SelfTest/String.tests.cpp @@ -0,0 +1,20 @@ +#include "../include/internal/catch_string.h" + +#include "catch.hpp" + +TEST_CASE( "String", "[Strings]" ) { + using Catch::String; + + SECTION( "empty string" ) { + String empty; + REQUIRE( empty.empty() ); + REQUIRE( empty.size() == 0 ); + REQUIRE( std::strcmp( empty.c_str(), "" ) == 0 ); + } + SECTION( "from literal" ) { + String s = "hello"; + REQUIRE( s.empty() == false ); + REQUIRE( s.size() == 5 ); + } + +} \ No newline at end of file diff --git a/projects/SelfTest/StringBuilder.tests.cpp b/projects/SelfTest/StringBuilder.tests.cpp new file mode 100644 index 00000000..e91d450d --- /dev/null +++ b/projects/SelfTest/StringBuilder.tests.cpp @@ -0,0 +1,76 @@ +#include "internal/catch_stringbuilder.h" +#include "../include/internal/catch_stringref.h" +#include "../include/internal/catch_string.h" + +#include "catch.hpp" + +TEST_CASE( "StringBuilder", "[Strings]" ) { + + using Catch::StringBuilder; + using Catch::String; + + StringBuilder sb; + + SECTION( "basic" ) { + REQUIRE( sb.capacity() == 0 ); + REQUIRE( sb.size() == 0 ); + + sb.reserve( 32 ); + REQUIRE( sb.capacity() == 32 ); + REQUIRE( sb.size() == 0 ); + + sb.append( "hello" ); + REQUIRE( sb.capacity() == 32 ); + REQUIRE( sb.size() == 5 ); + + String s = std::move( sb ); + REQUIRE( s == "hello" ); + REQUIRE( s.size() == 5 ); + } + + SECTION( "concatenation" ) { + sb << "hello" << " " << "world"; + String s = std::move( sb ); + REQUIRE( s == "hello world" ); + } + + SECTION( "concat & move" ) { + String s = StringBuilder() << "hello" << " " << "world"; + REQUIRE( s == "hello world" ); + } + + SECTION( "reserved" ) { + StringBuilder sb16( 16 ); + REQUIRE( sb16.capacity() == 16 ); + sb16 << "hello" << " " << "world"; + REQUIRE( sb16.capacity() == 16 ); + String s = std::move( sb16 ); + REQUIRE( s == "hello world" ); + } + + SECTION( "from String" ) { + String s = "hello"; + + SECTION( "copy" ) { + StringBuilder sb2 = s; + String s2( std::move(sb2) ); + REQUIRE( s2 == s ); + REQUIRE( s2.c_str() != s.c_str() ); + } + SECTION( "move from uniquely owned string" ) { + auto originalPointer = s.c_str(); + StringBuilder sb2( std::move( s ) ); + String s2( std::move(sb2) ); + REQUIRE( s2 == "hello" ); + REQUIRE( s2.c_str() == originalPointer ); + } + SECTION( "move from shared string (copies)" ) { + auto originalPointer = s.c_str(); + String keepAlive = s; + StringBuilder sb2( std::move( s ) ); + String s2( std::move(sb2) ); + REQUIRE( s2 == "hello" ); + REQUIRE( s2.c_str() != originalPointer ); + } + } +} diff --git a/projects/SelfTest/StringRef.tests.cpp b/projects/SelfTest/StringRef.tests.cpp new file mode 100644 index 00000000..e5da3f2a --- /dev/null +++ b/projects/SelfTest/StringRef.tests.cpp @@ -0,0 +1,151 @@ +#include "../include/internal/catch_stringref.h" +#include "../include/internal/catch_string.h" + +#include "catch.hpp" + +namespace Catch { + + // Implementation of test accessors + struct StringRefTestAccess { + static auto isOwned( StringRef const& stringRef ) -> bool { + return stringRef.isOwned(); + } + static auto isSubstring( StringRef const& stringRef ) -> bool { + return stringRef.isSubstring(); + } + static auto data( StringRef const& stringRef ) -> char const* { + return stringRef.data(); + } + }; + + auto isOwned( StringRef const& stringRef ) -> bool { + return StringRefTestAccess::isOwned( stringRef ); + } + auto isSubstring( StringRef const& stringRef ) -> bool { + return StringRefTestAccess::isSubstring( stringRef ); + } + auto data( StringRef const& stringRef ) -> char const* { + return StringRefTestAccess::data( stringRef ); + } +} // namespace Catch2 + +namespace Catch { + inline auto toString( Catch::StringRef const& stringRef ) -> std::string { + return std::string( data( stringRef ), stringRef.size() ); + } +} // namespace Catch + +TEST_CASE( "StringRef", "[Strings]" ) { + + using Catch::StringRef; + using Catch::String; + + 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 = data( 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 = data( ss ); + REQUIRE( rawChars == data( s ) ); // same pointer value + REQUIRE( ss.c_str() != rawChars ); + + REQUIRE( isSubstring( ss ) == false ); + REQUIRE( isOwned( ss ) ); + + REQUIRE( data( ss ) != data( 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") ); + } + + SECTION( "From string" ) { + String str = "hot potato"; + auto originalPointer = str.c_str(); + + SECTION( "Copied" ) { + // After a String is "copied" to a StringRef + // It has only copied the pointer and size + // - it does not take ownership + StringRef copied = str; + REQUIRE( copied == "hot potato" ); + REQUIRE( str == "hot potato" ); + REQUIRE( isOwned( copied ) == false ); + REQUIRE( data( copied ) == originalPointer ); + } + SECTION( "Moved" ) { + // After a String is *moved* to a StringRef + // The StringRef takes ownership of the underlying data + // and the String is left in an empty state + StringRef copied = std::move( str ); + REQUIRE( copied == "hot potato" ); + REQUIRE( isOwned( copied ) ); + REQUIRE( str.empty() ); + REQUIRE( data( copied ) == originalPointer ); + } + } +}