C++Spec 1.0.0
BDD testing for C++
Loading...
Searching...
No Matches
description.hpp
Go to the documentation of this file.
1
5#pragma once
6
7#include <deque>
8#include <forward_list>
9#include <source_location>
10#include <string>
11#include <utility>
12
13#include "it.hpp"
14
15namespace CppSpec {
16
17template <class T>
18class ClassDescription; // forward-declaration for ClassDescription
19
20class Description : public Runnable {
21 using VoidBlock = std::function<void()>;
22
23 public:
24 using Block = std::function<void(Description&)>;
25
26 std::forward_list<LetBase*> lets;
27 std::deque<VoidBlock> after_alls;
28 std::deque<VoidBlock> before_eaches;
29 std::deque<VoidBlock> after_eaches;
30
31 private:
32 Block block;
33
34 protected:
35 std::string description;
36
37 void exec_before_eaches();
38 void exec_after_eaches();
39
40 public:
41 // Primary constructor. Entry of all specs.
42 Description(const char* description,
43 Block block,
44 std::source_location location = std::source_location::current()) noexcept
45 : Runnable(location), block(std::move(block)), description(description) {
46 this->set_location(location);
47 }
48
49 Description(std::source_location location, std::string&& description) noexcept
50 : Runnable(location), description(std::move(description)) {}
51
52 Description(std::source_location location, const char* description, Block block) noexcept
53 : Runnable(location), block(std::move(block)), description(description) {}
54
55 /********* Specify/It *********/
56
57 ItD& it(const char* name, ItD::Block body, std::source_location location = std::source_location::current());
58 ItD& it(ItD::Block body, std::source_location location = std::source_location::current());
59
60 /********* Context ***********/
61
62 template <class T = std::nullptr_t>
63 Description& context(const char* description,
64 Block body,
65 std::source_location location = std::source_location::current());
66
67 template <Util::not_c_string T, class B>
68 ClassDescription<T>& context(T& subject, B block, std::source_location location = std::source_location::current());
69
70 template <class T, class B>
71 ClassDescription<T>& context(const char* description,
72 T& subject,
73 B block,
74 std::source_location location = std::source_location::current());
75
76 template <Util::not_c_string T, class B>
77 ClassDescription<T>& context(T&& subject, B block, std::source_location location = std::source_location::current());
78
79 template <class T, class B>
80 ClassDescription<T>& context(const char* description,
81 T&& subject,
82 B block,
83 std::source_location location = std::source_location::current());
84
85 template <class T, typename U>
86 ClassDescription<T>& context(std::initializer_list<U> init_list,
87 std::function<void(ClassDescription<T>&)> block,
88 std::source_location location = std::source_location::current());
89
90 /********* Each/All *********/
91
92 void before_each(VoidBlock block);
93 void before_all(VoidBlock block);
94 void after_each(VoidBlock block);
95 void after_all(VoidBlock block);
96
97 /********* Let *********/
98
99 template <typename T>
100 auto let(T body) -> Let<decltype(body())>;
101 void reset_lets() noexcept;
102
103 /********* Standard getters *********/
104
105 [[nodiscard]] virtual std::string get_description() const noexcept { return description; }
106 [[nodiscard]] virtual std::string get_subject_type() const noexcept { return ""; }
107
108 /********* Run *********/
109
110 void run() override;
111 // std::function<int(int, char **)>
112 template <typename Formatter>
113 inline auto as_main();
114};
115
116using Context = Description;
117
118/*>>>>>>>>>>>>>>>>>>>> Description <<<<<<<<<<<<<<<<<<<<<<<<<*/
119
120/*========= Description::it =========*/
121
122inline ItD& Description::it(const char* description, ItD::Block block, std::source_location location) {
123 auto* it = this->make_child<ItD>(location, description, block);
124 it->timed_run();
125 exec_after_eaches();
126 exec_before_eaches();
127 return *it;
128}
129
130inline ItD& Description::it(ItD::Block block, std::source_location location) {
131 auto* it = this->make_child<ItD>(location, block);
132 it->timed_run();
133 exec_after_eaches();
134 exec_before_eaches();
135 return *it;
136}
137
138/*========= Description::context =========*/
139
140template <class T>
141inline Context& Description::context(const char* description, Block body, std::source_location location) {
142 auto* context = this->make_child<Context>(location, description, body);
143 context->before_eaches = this->before_eaches;
144 context->after_eaches = this->after_eaches;
145 context->timed_run();
146 return *context;
147}
148
149/*========= Description:: each/alls =========*/
150
151inline void Description::before_each(VoidBlock b) {
152 before_eaches.push_back(b);
153
154 // Due to how lambdas and their contexts are passed around, we need to prime
155 // the environment by executing the before_each, so that when an 'it'
156 // declaration's lambda captures that env, it has the correct values for the
157 // variables. Truthfully, 'before_each' is a misnomer, as they are not
158 // getting executed directly before the lambda's execution as one might
159 // expect, but instead before the *next* lambda is declared.
160 b();
161}
162
163inline void Description::before_all(VoidBlock b) {
164 b();
165}
166
167inline void Description::after_each(VoidBlock b) {
168 after_eaches.push_back(b);
169}
170
171inline void Description::after_all(VoidBlock b) {
172 after_alls.push_back(b);
173}
174
175/*----------- private -------------*/
176
177inline void Description::exec_before_eaches() {
178 for (VoidBlock& b : before_eaches) {
179 b();
180 }
181}
182
183inline void Description::exec_after_eaches() {
184 for (VoidBlock& b : after_eaches) {
185 b();
186 }
187}
188
189/*========= Description::let =========*/
190
198template <typename T>
199auto Description::let(T block) -> Let<decltype(block())> {
200 // In reality, this gets inlined due to the fact that it's
201 // a templated function. Otherwise we wouldn't be able to
202 // add the address of the Let, return the Let by value,
203 // and still be able to do a valid deference of the Let
204 // pointer later on when we needed to reset the Let.
205
206 Let<decltype(block())> let(block); // Create a Let
207 lets.push_front(&let); // Add it to our list
208 return let; // Hand it object off
209}
210
211// TODO: Should this be protected?
212inline void Description::reset_lets() noexcept {
213 // For every let in our list, reset it.
214 for (auto& let : lets) {
215 let->reset();
216 }
217
218 // Recursively reset all the lets in the family tree
219 if (this->has_parent()) {
220 this->get_parent_as<Description>()->reset_lets();
221 }
222}
223
224/*========= Description::run =========*/
225
226inline void Description::run() {
227 block(*this); // Run the block
228 for (VoidBlock& a : after_alls) {
229 a(); // Run all our after_alls
230 }
231}
232
233/*>>>>>>>>>>>>>>>>>>>> ItD <<<<<<<<<<<<<<<<<<<<<<<<<*/
234
235/*========= ItD::run =========*/
236
237inline void ItD::run() {
238 block(*this);
239 this->get_parent_as<Description>()->reset_lets();
240}
241
242} // namespace CppSpec
A Description with a defined subject.
Definition class_description.hpp:22
Definition description.hpp:20
auto let(T body) -> Let< decltype(body())>
Object generator for Let.
Definition description.hpp:199
An it embedded in a Description.
Definition it.hpp:27
A container that memoizes the result of a block in `it's.
Definition let.hpp:34
bool has_parent() noexcept
Check to see if the Runnable has a parent.
Definition runnable.hpp:56