Specs
Beautiful C++ Test Framework
Loading...
Searching...
No Matches
GlobalSpecGroup.h
Go to the documentation of this file.
1#pragma once
2
3#include <Specs/API.h>
7#include <Specs/SpecGroup.h>
8#include <Specs/SpecSetup.h>
10#include <Specs/SpecTeardown.h>
11#include <Specs/SpecTest.h>
12#include <_Log_.h>
13#include <collections.h>
14
15#include <memory>
16#include <string>
17#include <string_view>
18#include <vector>
19
20namespace SpecsCpp {
21
23 static constexpr auto META_DATA_KEY_TIMEOUT = "timeout";
24 static constexpr auto META_DATA_KEY_SKIP = "skip";
25
26 std::vector<ISpecGroup*> _currentGroupStack;
27 std::vector<ISpecGroup*> _savedGroupStack;
28
29 ISpecGroup* _currentFileGroup = nullptr;
30 ISpecGroup* _savedFileGroup = nullptr;
31 ISpecGroup* _currentTopLevelGroup = nullptr;
32 ISpecGroup* _savedTopLevelGroup = nullptr;
33
34 // A place to store some memory! These go here and never leave :)
35 std::vector<std::unique_ptr<SpecTest>> _registeredSpecs;
36 std::vector<std::unique_ptr<SpecSetup>> _registeredsetups;
37 std::vector<std::unique_ptr<SpecTeardown>> _registeredteardowns;
38 std::vector<std::unique_ptr<SpecGroup>> _registeredGroups;
39 std::vector<std::unique_ptr<SpecSetup>> _registeredOneTimesetups;
40 std::vector<std::unique_ptr<SpecTeardown>> _registeredOneTimeteardowns;
41
42 collections_map<std::string, std::unique_ptr<SpecGroup>> _testTemplateGroups;
43
44 SpecDataValueCollection _metaDataForNextComponent;
45 SpecTagCollection _tagsForNextComponent;
46
47 void component_defined(ISpecComponent* component) {
48 // Built-in support for "skip"
49 if (_metaDataForNextComponent.has(META_DATA_KEY_SKIP)) {
50 auto skip = _metaDataForNextComponent.get(META_DATA_KEY_SKIP)->bool_value();
51 component->mark_skipped(skip);
52 }
53
54 // Built-in support for "timeout"
55 if (_metaDataForNextComponent.has(META_DATA_KEY_TIMEOUT)) {
56 if (auto* hasCodeBlock = dynamic_cast<ISpecHasCodeBlock*>(component)) {
57 auto timeout =
58 _metaDataForNextComponent.get(META_DATA_KEY_TIMEOUT)->int_value();
59 hasCodeBlock->code_block()->set_timeout_ms(timeout);
60 }
61 }
62
63 component->data()->merge(&_metaDataForNextComponent);
64 component->tags()->merge(&_tagsForNextComponent);
65 _metaDataForNextComponent.clear();
66 _tagsForNextComponent.clear();
67 }
68
69 public:
71 // GlobalSpecGroup comes with a root group
72 auto rootGroup = std::make_unique<SpecGroup>(nullptr, "");
73 _registeredGroups.push_back(std::move(rootGroup));
74 _currentGroupStack.push_back(_registeredGroups.back().get());
75 }
76
77 // Or seed it with your own root group
78 GlobalSpecGroup(ISpecGroup* rootGroup) { _currentGroupStack.push_back(rootGroup); }
79
82 return instance;
83 }
84
85 ISpecGroup* root() const { return _registeredGroups.front().get(); }
86 ISpecGroup* get() const {
87 return _currentGroupStack.empty() ? nullptr : _currentGroupStack.back();
88 }
89 void set(ISpecGroup* group) { _currentGroupStack.back() = group; }
90 void push(ISpecGroup* group) { _currentGroupStack.push_back(group); }
91 void pop() { _currentGroupStack.pop_back(); }
92
94 _currentGroupStack.clear();
95
96 // This makes the current group / file notes no longer valid
97 _currentFileGroup = nullptr;
98 _currentTopLevelGroup = nullptr;
99 }
100
103 if (auto* rootGroup = root()) push(rootGroup);
104 }
105
107 _savedFileGroup = _currentFileGroup;
108 _savedTopLevelGroup = _currentTopLevelGroup;
109 _savedGroupStack = _currentGroupStack;
110 }
111
113 _currentFileGroup = _savedFileGroup;
114 _currentTopLevelGroup = _savedTopLevelGroup;
115 _currentGroupStack = _savedGroupStack;
116 }
117
119 std::string_view templateName, std::unique_ptr<SpecCodeBlock> codeBlock
120 ) {
121 auto templateGroup = std::make_unique<SpecGroup>(nullptr, templateName);
122 auto* templateGroupPtr = templateGroup.get();
123 component_defined(templateGroupPtr);
124
125 auto* currentGroup = get();
126 set(templateGroupPtr);
127 codeBlock->run(templateGroupPtr, templateGroupPtr, nullptr, nullptr);
128 set(currentGroup);
129
130 _testTemplateGroups[templateName.data()] = std::move(templateGroup);
131 }
132
134 std::string_view description, std::unique_ptr<SpecCodeBlock> codeBlock,
135 bool removeUnderscores = false
136 ) {
137 if (auto* group = get()) {
138 std::string descriptionText{description};
139 if (removeUnderscores) {
140 std::replace(descriptionText.begin(), descriptionText.end(), '_', ' ');
141 description = descriptionText;
142 }
143 auto specGroup = std::make_unique<SpecGroup>(get(), descriptionText);
144 auto* specGroupPtr = specGroup.get();
145 component_defined(specGroupPtr);
146
147 _registeredGroups.push_back(std::move(specGroup));
148 group->add_group(_registeredGroups.back().get());
149
150 // Evaluate the group block (with this group being the 'current group')
151 push(specGroupPtr);
152 codeBlock->run(specGroupPtr, specGroupPtr, nullptr, nullptr);
153 pop();
154 } else {
155 _Log_("define_group() called but no group is active!");
156 }
157 }
158
160 std::string_view description, std::unique_ptr<SpecCodeBlock> codeBlock,
161 bool skip = false
162 ) {
163 if (auto* group = get()) {
164 auto spec = std::make_unique<SpecTest>(get(), description, std::move(codeBlock));
165 if (skip) spec->mark_skipped(true);
166 auto* specPtr = spec.get();
167 component_defined(specPtr);
168
169 _registeredSpecs.push_back(std::move(spec));
170 group->add_test(specPtr);
171 } else {
172 _Log_("define_spec() called but no group is active!");
173 }
174 }
175
176 void define_setup(std::unique_ptr<SpecCodeBlock> codeBlock, bool skip = false) {
177 if (auto* group = get()) {
178 auto setup = std::make_unique<SpecSetup>(get(), std::move(codeBlock));
179 if (skip) setup->mark_skipped(true);
180 auto* setupPtr = setup.get();
181 component_defined(setupPtr);
182
183 _registeredsetups.push_back(std::move(setup));
184 group->add_setup(setupPtr);
185 }
186 }
187
188 void define_teardown(std::unique_ptr<SpecCodeBlock> codeBlock, bool skip = false) {
189 if (auto* group = get()) {
190 auto teardown = std::make_unique<SpecTeardown>(get(), std::move(codeBlock));
191 if (skip) teardown->mark_skipped(true);
192 auto* teardownPtr = teardown.get();
193 component_defined(teardownPtr);
194
195 _registeredteardowns.push_back(std::move(teardown));
196 group->add_teardown(teardownPtr);
197 }
198 }
199
200 void define_one_time_setup(std::unique_ptr<SpecCodeBlock> codeBlock, bool skip = false) {
201 if (auto* group = get()) {
202 auto setup = std::make_unique<SpecSetup>(get(), std::move(codeBlock));
203 if (skip) setup->mark_skipped(true);
204 auto* setupPtr = setup.get();
205 component_defined(setupPtr);
206
207 _registeredOneTimesetups.push_back(std::move(setup));
208 group->add_one_time_setup(setupPtr);
209 }
210 }
211
212 void define_one_time_teardown(std::unique_ptr<SpecCodeBlock> codeBlock, bool skip = false) {
213 if (auto* group = get()) {
214 auto teardown = std::make_unique<SpecTeardown>(get(), std::move(codeBlock));
215 if (skip) teardown->mark_skipped(true);
216 auto* teardownPtr = teardown.get();
217 component_defined(teardownPtr);
218
219 _registeredOneTimeteardowns.push_back(std::move(teardown));
220 group->add_one_time_teardown(teardownPtr);
221 }
222 }
223
225 if (auto* group = get()) {
226 // Is there another top-level group? Let's pop it off
227 if (_currentTopLevelGroup) {
228 while (get() != _currentTopLevelGroup) pop();
229 if (get() == _currentTopLevelGroup) pop();
230 }
231 _currentTopLevelGroup = nullptr;
232 }
233 }
234
235 void declare_top_level_group(std::string_view description) {
237
238 if (auto* group = get()) {
239 auto specGroup = std::make_unique<SpecGroup>(get(), description);
240 auto* specGroupPtr = specGroup.get();
241 component_defined(specGroupPtr);
242
243 _currentTopLevelGroup = specGroupPtr;
244 _registeredGroups.push_back(std::move(specGroup));
245 group->add_group(specGroupPtr);
246 push(specGroupPtr);
247 }
248 }
249
251 if (auto* group = get()) {
252 // Is there another top-level group? Let's pop it off
253 if (_currentFileGroup) {
254 while (get() != _currentFileGroup) pop();
255 if (get() == _currentFileGroup) pop();
256 }
257 _currentFileGroup = nullptr;
258 }
259 }
260
261 void declare_file_group(std::string_view description, bool removeUnderscores = true) {
264
265 if (auto* group = get()) {
266 std::string descriptionText{description};
267 if (removeUnderscores) {
268 std::replace(descriptionText.begin(), descriptionText.end(), '_', ' ');
269 description = descriptionText;
270 }
271 auto specGroup = std::make_unique<SpecGroup>(get(), descriptionText);
272 auto* specGroupPtr = specGroup.get();
273 component_defined(specGroupPtr);
274
275 _currentFileGroup = specGroupPtr;
276 _registeredGroups.push_back(std::move(specGroup));
277 group->add_group(specGroupPtr);
278 push(specGroupPtr);
279 }
280 }
281
283 std::string_view templateName, bool clearFileGroup = false,
284 bool removeUnderscores = true
285 ) {
287 if (clearFileGroup) clear_file_group();
288
289 if (auto* group = get()) {
290 std::string descriptionText{templateName};
291 if (removeUnderscores) {
292 std::replace(descriptionText.begin(), descriptionText.end(), '_', ' ');
293 templateName = descriptionText;
294 }
295 auto foundExisting = _testTemplateGroups.find(descriptionText.data());
296 if (foundExisting != _testTemplateGroups.end()) {
297 clear_group_stack(); // The top-level things should NOT be registered anywhere
298 } else {
299 auto specGroup = std::make_unique<SpecGroup>(get(), descriptionText);
300 auto* specGroupPtr = specGroup.get();
301 component_defined(specGroupPtr);
302
303 _currentTopLevelGroup = specGroupPtr;
304 _testTemplateGroups[descriptionText] = std::move(specGroup);
305 push(specGroupPtr);
306 }
307 }
308 }
309
310 void declare_group(std::string_view description, bool removeUnderscores = true) {
311 if (auto* group = get()) {
312 std::string descriptionText{description};
313 if (removeUnderscores) {
314 std::replace(descriptionText.begin(), descriptionText.end(), '_', ' ');
315 description = descriptionText;
316 }
317 auto specGroup = std::make_unique<SpecGroup>(get(), descriptionText);
318 auto* specGroupPtr = specGroup.get();
319 component_defined(specGroupPtr);
320
321 _registeredGroups.push_back(std::move(specGroup));
322 group->add_group(_registeredGroups.back().get());
323 push(specGroupPtr);
324 }
325 }
326
327 void declare_groups(std::vector<std::string> descriptions) {
328 for (auto& description : descriptions) declare_group(description);
329 }
330
331 void use_template(std::string_view templateName) {
332 if (auto* group = get()) {
333 auto found = _testTemplateGroups.find(templateName.data());
334 if (found == _testTemplateGroups.end()) {
335 _Error_("Test template not found: {}", templateName);
336 return;
337 }
338
339 auto* templateGroup = found->second.get();
340 group->merge(templateGroup);
341 }
342 }
343
344 void use_templates(std::vector<std::string_view> templateNames) {
345 if (auto* group = get())
346 for (auto& templateName : templateNames) use_template(templateName);
347 }
348
350 _metaDataForNextComponent.add(value);
351 }
352
353 void add_tag_for_next_component(std::string_view value) {
354 _tagsForNextComponent.add(value.data());
355 }
356
357 void add_tags_for_next_component(std::vector<std::string_view> values) {
358 for (auto& value : values) add_tag_for_next_component(value.data());
359 }
360 };
361}
void add_tag_for_next_component(std::string_view value)
void push(ISpecGroup *group)
void use_templates(std::vector< std::string_view > templateNames)
static GlobalSpecGroup & instance()
void define_group(std::string_view description, std::unique_ptr< SpecCodeBlock > codeBlock, bool removeUnderscores=false)
void define_teardown(std::unique_ptr< SpecCodeBlock > codeBlock, bool skip=false)
void define_spec(std::string_view description, std::unique_ptr< SpecCodeBlock > codeBlock, bool skip=false)
void define_setup(std::unique_ptr< SpecCodeBlock > codeBlock, bool skip=false)
void define_one_time_teardown(std::unique_ptr< SpecCodeBlock > codeBlock, bool skip=false)
void declare_groups(std::vector< std::string > descriptions)
void use_template(std::string_view templateName)
void define_one_time_setup(std::unique_ptr< SpecCodeBlock > codeBlock, bool skip=false)
ISpecGroup * root() const
void declare_top_level_group(std::string_view description)
void set(ISpecGroup *group)
void add_meta_data_for_next_component(ISpecDataValue *value)
GlobalSpecGroup(ISpecGroup *rootGroup)
void define_template(std::string_view templateName, std::unique_ptr< SpecCodeBlock > codeBlock)
ISpecGroup * get() const
void declare_file_group(std::string_view description, bool removeUnderscores=true)
void add_tags_for_next_component(std::vector< std::string_view > values)
void declare_group(std::string_view description, bool removeUnderscores=true)
void declare_top_level_template(std::string_view templateName, bool clearFileGroup=false, bool removeUnderscores=true)
ISpecDataValue * get(const char *key) const override
bool has(const char *key) const override
void add(ISpecDataValue *value) override
void add(const char *value) override
Definition API.h:3
virtual ISpecDataValueCollection * data() const =0
virtual void mark_skipped(bool skip=true)=0
virtual ISpecTagCollection * tags() const =0
virtual void merge(ISpecDataValueCollection *)=0
virtual int int_value() const =0
virtual bool bool_value() const =0
virtual void add_group(ISpecGroup *)=0
virtual void merge(ISpecTagCollection *)=0