Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework INFO lazy evaluation to use lambdas. #270

Merged
merged 8 commits into from Aug 12, 2019
169 changes: 22 additions & 147 deletions doctest/doctest.h
Expand Up @@ -1528,107 +1528,25 @@ namespace detail {
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in);
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);

class DOCTEST_INTERFACE ContextBuilder
{
friend class ContextScope;

struct DOCTEST_INTERFACE ICapture
{
DOCTEST_DELETE_COPIES(ICapture);
ICapture();
virtual ~ICapture();
virtual void toStream(std::ostream*) const = 0;
};

template <typename T>
struct Capture : public ICapture //!OCLINT destructor of virtual class
{
const T* capture;

explicit Capture(const T* in)
: capture(in) {}
void toStream(std::ostream* s) const override { detail::toStream(s, *capture); }
};

struct DOCTEST_INTERFACE Chunk
{
char buf[sizeof(Capture<char>)] DOCTEST_ALIGNMENT(
2 * sizeof(void*)); // place to construct a Capture<T>

DOCTEST_DECLARE_DEFAULTS(Chunk);
DOCTEST_DELETE_COPIES(Chunk);
};

struct DOCTEST_INTERFACE Node
{
Chunk chunk;
Node* next;

DOCTEST_DECLARE_DEFAULTS(Node);
DOCTEST_DELETE_COPIES(Node);
};

Chunk stackChunks[DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK];
int numCaptures = 0;
Node* head = nullptr;
Node* tail = nullptr;
class ContextScopeBase : public IContextScope {
protected:
ContextScopeBase();

ContextBuilder(ContextBuilder& other);

ContextBuilder& operator=(const ContextBuilder&) = delete;

void stringify(std::ostream* s) const;

public:
ContextBuilder();
~ContextBuilder();

template <typename T>
DOCTEST_NOINLINE ContextBuilder& operator<<(T& in) {
Capture<T> temp(&in);

// construct either on stack or on heap
// copy the bytes for the whole object - including the vtable because we cant construct
// the object directly in the buffer using placement new - need the <new> header...
if(numCaptures < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK) {
my_memcpy(stackChunks[numCaptures].buf, &temp, sizeof(Chunk));
} else {
auto curr = new Node;
curr->next = nullptr;
if(tail) {
tail->next = curr;
tail = curr;
} else {
head = tail = curr;
}

my_memcpy(tail->chunk.buf, &temp, sizeof(Chunk));
}
++numCaptures;
return *this;
}

template <typename T>
ContextBuilder& operator<<(const T&&) {
static_assert(deferred_false<T>::value,
"Cannot pass temporaries or rvalues to the streaming operator because it "
"caches pointers to the passed objects for lazy evaluation!");
return *this;
}
void destroy();
};

class DOCTEST_INTERFACE ContextScope : public IContextScope
template <typename L> class DOCTEST_INTERFACE ContextScope : public ContextScopeBase
{
ContextBuilder contextBuilder;
const L &lambda_;

public:
explicit ContextScope(ContextBuilder& temp);
explicit ContextScope(const L &lambda) : lambda_(lambda) {};

DOCTEST_DELETE_COPIES(ContextScope);

~ContextScope() override;
void stringify(std::ostream* s) const override { lambda_(s); }

void stringify(std::ostream* s) const override;
~ContextScope() override { destroy(); };
DaanDeMeyer marked this conversation as resolved.
Show resolved Hide resolved
};

struct DOCTEST_INTERFACE MessageBuilder : public MessageData
Expand Down Expand Up @@ -2057,9 +1975,17 @@ int registerReporter(const char* name, int priority) {
DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)

// for logging
#define DOCTEST_INFO(x) \
doctest::detail::ContextScope DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)( \
doctest::detail::ContextBuilder() << x)
#define DOCTEST_INFO(expression) \
DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression)

#define DOCTEST_INFO_IMPL(lambda_name, expression) \
auto lambda_name = [&](std::ostream* s) { \
doctest::detail::MessageBuilder mb(__FILE__, __LINE__, doctest::assertType::is_warn); \
DaanDeMeyer marked this conversation as resolved.
Show resolved Hide resolved
mb.m_stream = s; \
mb << expression; \
}; \
doctest::detail::ContextScope<decltype(lambda_name)> DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)(lambda_name)

#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x)

#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \
Expand Down Expand Up @@ -3964,64 +3890,14 @@ namespace detail {

DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()

ContextBuilder::ICapture::ICapture() = default;
ContextBuilder::ICapture::~ICapture() = default;

ContextBuilder::Chunk::Chunk() = default;
ContextBuilder::Chunk::~Chunk() = default;

ContextBuilder::Node::Node() = default;
ContextBuilder::Node::~Node() = default;

// steal the contents of the other - acting as a move constructor...
ContextBuilder::ContextBuilder(ContextBuilder& other)
: numCaptures(other.numCaptures)
, head(other.head)
, tail(other.tail) {
other.numCaptures = 0;
other.head = nullptr;
other.tail = nullptr;
memcpy(stackChunks, other.stackChunks,
unsigned(int(sizeof(Chunk)) * DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK));
}

DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcast-align")
void ContextBuilder::stringify(std::ostream* s) const {
int curr = 0;
// iterate over small buffer
while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)
reinterpret_cast<const ICapture*>(stackChunks[curr++].buf)->toStream(s);
// iterate over list
auto curr_elem = head;
while(curr < numCaptures) {
reinterpret_cast<const ICapture*>(curr_elem->chunk.buf)->toStream(s);
curr_elem = curr_elem->next;
++curr;
}
}
DOCTEST_GCC_SUPPRESS_WARNING_POP

ContextBuilder::ContextBuilder() = default;

ContextBuilder::~ContextBuilder() {
// free the linked list - the ones on the stack are left as-is
// no destructors are called at all - there is no need
while(head) {
auto next = head->next;
delete head;
head = next;
}
}

ContextScope::ContextScope(ContextBuilder& temp)
: contextBuilder(temp) {
ContextScopeBase::ContextScopeBase() {
g_infoContexts.push_back(this);
}

DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
ContextScope::~ContextScope() {
void ContextScopeBase::destroy() {
if(std::uncaught_exception()) {
std::ostringstream s;
this->stringify(&s);
Expand All @@ -4033,7 +3909,6 @@ namespace detail {
DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP

void ContextScope::stringify(std::ostream* s) const { contextBuilder.stringify(s); }
} // namespace detail
namespace {
using namespace detail;
Expand Down
55 changes: 2 additions & 53 deletions doctest/parts/doctest.cpp
Expand Up @@ -1267,64 +1267,14 @@ namespace detail {

DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()

ContextBuilder::ICapture::ICapture() = default;
ContextBuilder::ICapture::~ICapture() = default;

ContextBuilder::Chunk::Chunk() = default;
ContextBuilder::Chunk::~Chunk() = default;

ContextBuilder::Node::Node() = default;
ContextBuilder::Node::~Node() = default;

// steal the contents of the other - acting as a move constructor...
ContextBuilder::ContextBuilder(ContextBuilder& other)
: numCaptures(other.numCaptures)
, head(other.head)
, tail(other.tail) {
other.numCaptures = 0;
other.head = nullptr;
other.tail = nullptr;
memcpy(stackChunks, other.stackChunks,
unsigned(int(sizeof(Chunk)) * DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK));
}

DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcast-align")
void ContextBuilder::stringify(std::ostream* s) const {
int curr = 0;
// iterate over small buffer
while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)
reinterpret_cast<const ICapture*>(stackChunks[curr++].buf)->toStream(s);
// iterate over list
auto curr_elem = head;
while(curr < numCaptures) {
reinterpret_cast<const ICapture*>(curr_elem->chunk.buf)->toStream(s);
curr_elem = curr_elem->next;
++curr;
}
}
DOCTEST_GCC_SUPPRESS_WARNING_POP

ContextBuilder::ContextBuilder() = default;

ContextBuilder::~ContextBuilder() {
// free the linked list - the ones on the stack are left as-is
// no destructors are called at all - there is no need
while(head) {
auto next = head->next;
delete head;
head = next;
}
}

ContextScope::ContextScope(ContextBuilder& temp)
: contextBuilder(temp) {
ContextScopeBase::ContextScopeBase() {
g_infoContexts.push_back(this);
}

DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
ContextScope::~ContextScope() {
void ContextScopeBase::destroy() {
if(std::uncaught_exception()) {
std::ostringstream s;
this->stringify(&s);
Expand All @@ -1336,7 +1286,6 @@ namespace detail {
DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP

void ContextScope::stringify(std::ostream* s) const { contextBuilder.stringify(s); }
} // namespace detail
namespace {
using namespace detail;
Expand Down