mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-22 21:36:11 +01:00
Added string classes
This commit is contained in:
parent
cb0a5194af
commit
78e7994435
@ -75,6 +75,9 @@ set(TEST_SOURCES
|
|||||||
${SELF_TEST_DIR}/TrickyTests.cpp
|
${SELF_TEST_DIR}/TrickyTests.cpp
|
||||||
${SELF_TEST_DIR}/VariadicMacrosTests.cpp
|
${SELF_TEST_DIR}/VariadicMacrosTests.cpp
|
||||||
${SELF_TEST_DIR}/MatchersTests.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})
|
CheckFileList(TEST_SOURCES ${SELF_TEST_DIR})
|
||||||
|
|
||||||
@ -181,6 +184,14 @@ set(INTERNAL_HEADERS
|
|||||||
${HEADER_DIR}/internal/catch_stream.h
|
${HEADER_DIR}/internal/catch_stream.h
|
||||||
${HEADER_DIR}/internal/catch_stream.hpp
|
${HEADER_DIR}/internal/catch_stream.hpp
|
||||||
${HEADER_DIR}/internal/catch_streambuf.h
|
${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_suppress_warnings.h
|
||||||
${HEADER_DIR}/internal/catch_tag_alias.h
|
${HEADER_DIR}/internal/catch_tag_alias.h
|
||||||
${HEADER_DIR}/internal/catch_tag_alias_registry.h
|
${HEADER_DIR}/internal/catch_tag_alias_registry.h
|
||||||
@ -243,6 +254,8 @@ SOURCE_GROUP("Benchmarks" FILES ${BENCH_SOURCES})
|
|||||||
# configure the executable
|
# configure the executable
|
||||||
include_directories(${HEADER_DIR})
|
include_directories(${HEADER_DIR})
|
||||||
|
|
||||||
|
add_definitions( -DCATCH_CONFIG_FULL_PROJECT )
|
||||||
|
|
||||||
# Projects consuming Catch via ExternalProject_Add might want to use install step
|
# Projects consuming Catch via ExternalProject_Add might want to use install step
|
||||||
# without building all of our selftests.
|
# without building all of our selftests.
|
||||||
if (NOT NO_SELFTEST)
|
if (NOT NO_SELFTEST)
|
||||||
|
@ -37,6 +37,15 @@
|
|||||||
#include "catch_matchers_string.hpp"
|
#include "catch_matchers_string.hpp"
|
||||||
#include "catch_startup_exception_registry.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_multi.hpp"
|
||||||
#include "../reporters/catch_reporter_xml.hpp"
|
#include "../reporters/catch_reporter_xml.hpp"
|
||||||
#include "../reporters/catch_reporter_junit.hpp"
|
#include "../reporters/catch_reporter_junit.hpp"
|
||||||
|
83
include/internal/catch_string.cpp
Normal file
83
include/internal/catch_string.cpp
Normal file
@ -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 <ostream>
|
||||||
|
|
||||||
|
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<StringData*>( 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
|
48
include/internal/catch_string.h
Normal file
48
include/internal/catch_string.h
Normal file
@ -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
|
111
include/internal/catch_stringbuilder.cpp
Normal file
111
include/internal/catch_stringbuilder.cpp
Normal file
@ -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 <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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<StringData*&>( 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
|
72
include/internal/catch_stringbuilder.h
Normal file
72
include/internal/catch_stringbuilder.h
Normal file
@ -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
|
49
include/internal/catch_stringdata.cpp
Normal file
49
include/internal/catch_stringdata.cpp
Normal file
@ -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 <new>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
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
|
52
include/internal/catch_stringdata.h
Normal file
52
include/internal/catch_stringdata.h
Normal file
@ -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 <atomic>
|
||||||
|
|
||||||
|
namespace Catch {
|
||||||
|
|
||||||
|
class StringRef;
|
||||||
|
|
||||||
|
class StringData {
|
||||||
|
mutable std::atomic<unsigned int> 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<char const*>( 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
|
140
include/internal/catch_stringref.cpp
Normal file
140
include/internal/catch_stringref.cpp
Normal file
@ -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 <cstring>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
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<StringRef*>( 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
|
84
include/internal/catch_stringref.h
Normal file
84
include/internal/catch_stringref.h
Normal file
@ -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
|
20
projects/SelfTest/String.tests.cpp
Normal file
20
projects/SelfTest/String.tests.cpp
Normal file
@ -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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
76
projects/SelfTest/StringBuilder.tests.cpp
Normal file
76
projects/SelfTest/StringBuilder.tests.cpp
Normal file
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
151
projects/SelfTest/StringRef.tests.cpp
Normal file
151
projects/SelfTest/StringRef.tests.cpp
Normal file
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user