Specs
Beautiful C++ Test Framework
Loading...
Searching...
No Matches
SpecSerialRunner.h
Go to the documentation of this file.
1#pragma once
2
3#include <Specs/API.h>
6#include <_Log_.h>
7
8#include <future>
9#include <memory>
10#include <regex>
11#include <string>
12#include <vector>
13
14namespace SpecsCpp {
15
16 // TODO: separate a bunch of common code into a SpecRunner class
17 // which the parallel runner will also inherit from :)
18 //
19 // Also: perhaps go ahead and build a full list of all specs to be
20 // run before running any of them, so we can print out a summary BEFORE running
21 // and also this lets us do randomization :)
22
24 class SpecSuiteRunInstance {
25 SpecSuiteRunResult _resultTotalCounts;
26 ISpecReporterCollection* _reporters;
27 ISpecRunOptions* _options = nullptr;
28
29 int _timeoutMs = 3000;
30 // int _timeoutMs = 0;
31
32 bool _currentlySkippingTests = false;
33 int _setupGroupsRun = 0;
34 std::unique_ptr<std::promise<ISpecRunResult*>> _currentRunResultPromise = nullptr;
35 ISpecGroup* _currentGroup = nullptr;
36 ISpec* _currentSpec = nullptr;
37 bool _currentSpecFailed = false;
38 std::unique_ptr<ISpecRunResult> _currentResult = nullptr;
39 std::string _currentSpecFailureMessage;
40
41 inline bool list_only() const { return false; }
42
43 inline bool any_text_matches(const char* text, ISpecRunTextOptionList* textList) {
44 // if (!filter || !description) return false;
45 // bool result = strstr(description, filter) != nullptr;
46 // return strstr(description, filter) != nullptr;
47 return false;
48 }
49
50 inline bool any_regex_matches(const char* text, ISpecRunTextOptionList* textList) {
51 // if (!filter || !description) return false;
52 // return std::regex_search(description, std::regex(filter));
53 return false;
54 }
55
56 inline bool should_run_group(ISpecGroup* group) {
57 // Always run root group (which has empty string as description)
58 if (strlen(group->description()) == 0) return true;
59 if (group->skip()) return false;
60 // if (description_matches(group->full_description(), filter_text())) return true;
61 // if (description_matches(group->description(), group_filter_text())) return true;
62 // if (regex_description_matches(group->full_description(), regex_filter_text()))
63 // return true;
64 // if (regex_description_matches(group->description(), group_regex_filter_text()))
65 // return true;
66 // return (
67 // filter_text() == nullptr && group_filter_text() == nullptr &&
68 // regex_filter_text() == nullptr && group_regex_filter_text() == nullptr
69 // );
70 return true;
71 }
72
73 inline bool should_run_spec(ISpec* spec) {
74 if (spec->skip()) return false;
75 // if (description_matches(spec->full_description(), filter_text())) return true;
76 // if (description_matches(spec->description(), spec_filter_text())) return true;
77 // if (regex_description_matches(spec->full_description(), regex_filter_text()))
78 // return true;
79 // if (regex_description_matches(spec->description(), spec_regex_filter_text()))
80 // return true;
81 // return (
82 // filter_text() == nullptr && spec_filter_text() == nullptr &&
83 // regex_filter_text() == nullptr && spec_regex_filter_text() == nullptr
84 // );
85 return true;
86 }
87
88 void code_block_callback_fn(ISpecRunResult* result) {
89 if (result->status() == RunResultStatus::Failed ||
90 result->status() == RunResultStatus::Timeout) {
91 _currentSpecFailed = true;
92 _currentSpecFailureMessage = result->message();
93 }
94 _currentResult = std::unique_ptr<ISpecRunResult>(result->copy());
95 _currentRunResultPromise->set_value(_currentResult.get());
96 }
97
98 FunctionPointer<void(ISpecRunResult*)> _codeBlockCallbackFn{
99 this, &SpecSuiteRunInstance::code_block_callback_fn
100 };
101
102 void foreach_setup_in_group(ISpecSetup* setup) {
103 if (_currentSpecFailed || setup->skip()) {
104 auto result = SpecRunResult::not_run(setup, _currentGroup, _currentSpec);
105 _reporters->report_setup(result.get());
106 return;
107 }
108
109 _currentRunResultPromise = std::make_unique<std::promise<ISpecRunResult*>>();
110
111 auto* codeBlock = setup->code_block();
112 if (codeBlock->get_timeout_ms() == 0) codeBlock->set_timeout_ms(_timeoutMs);
113
114 codeBlock->run(setup, _currentGroup, _currentSpec, &_codeBlockCallbackFn);
115
116 auto future = _currentRunResultPromise->get_future();
117 if (codeBlock->get_timeout_ms() > 0) {
118 if (future.wait_for(std::chrono::milliseconds(codeBlock->get_timeout_ms())) ==
119 std::future_status::timeout) {
120 auto result = SpecRunResult::timeout(setup, _currentGroup, _currentSpec);
121 _reporters->report_setup(result.get());
122 _currentSpecFailed = true;
123 return;
124 }
125 }
126
127 if (auto* result = future.get()) _reporters->report_setup(result);
128 else _Log_("Setup callback future.get() returned nullptr");
129 }
130
131 FunctionPointer<void(ISpecSetup*)> _forEachSetupInGroupFn{
132 this, &SpecSuiteRunInstance::foreach_setup_in_group
133 };
134
135 void foreach_teardown_in_group(ISpecTeardown* teardown) {
136 if (teardown->skip()) return;
137
138 _currentRunResultPromise = std::make_unique<std::promise<ISpecRunResult*>>();
139
140 auto* codeBlock = teardown->code_block();
141 if (codeBlock->get_timeout_ms() == 0) codeBlock->set_timeout_ms(_timeoutMs);
142
143 codeBlock->run(teardown, _currentGroup, _currentSpec, &_codeBlockCallbackFn);
144
145 auto future = _currentRunResultPromise->get_future();
146 if (codeBlock->get_timeout_ms() > 0) {
147 if (future.wait_for(std::chrono::milliseconds(codeBlock->get_timeout_ms())) ==
148 std::future_status::timeout) {
149 auto result = SpecRunResult::timeout(teardown, _currentGroup, _currentSpec);
150 _reporters->report_teardown(result.get());
151 _currentSpecFailed = true;
152 return;
153 }
154 }
155
156 if (auto* result = future.get()) _reporters->report_teardown(result);
157 else _Log_("Teardown callback future.get() returned nullptr");
158 }
159
160 FunctionPointer<void(ISpecTeardown*)> _forEachTeardownInGroupFn{
161 this, &SpecSuiteRunInstance::foreach_teardown_in_group
162 };
163
164 void foreach_test_in_group(ISpec* spec) {
165 if (_currentlySkippingTests || list_only() || !should_run_spec(spec)) {
166 auto result = SpecRunResult::not_run(spec, _currentGroup, spec);
167 _reporters->report_test_result(result.get());
168 _resultTotalCounts.increment_not_run();
169 return;
170 }
171
172 _currentSpec = spec;
173 _currentSpecFailed = false;
174 _currentResult = nullptr;
175
176 _reporters->report_test_begin(_currentGroup, _currentSpec);
177
178 std::vector<ISpecGroup*> groupStack;
179 auto* currentGroup = _currentGroup;
180 while (currentGroup) {
181 groupStack.push_back(currentGroup);
182 currentGroup = currentGroup->group();
183 }
184
185 // Run foreach blocks, starting with root parent
186 for (auto it = groupStack.rbegin(); it != groupStack.rend(); ++it) {
187 auto* group = *it;
188 group->foreach_setup(&_forEachSetupInGroupFn);
189 if (!_currentSpecFailed) _setupGroupsRun++;
190 }
191
192 if (_currentSpecFailed) {
193 auto specCodeResult = SpecRunResult::not_run(spec, _currentGroup, spec);
194 _reporters->report_test(specCodeResult.get());
195
196 if (_currentResult && _currentResult->status() == RunResultStatus::Timeout) {
197 auto specFinalResult = SpecRunResult::timeout(spec, _currentGroup, spec);
198 _reporters->report_test_result(specFinalResult.get());
199 } else {
200 auto specFinalResult = SpecRunResult::failed(
201 spec, _currentGroup, spec, _currentSpecFailureMessage
202 );
203 _reporters->report_test_result(specFinalResult.get());
204 }
205
206 _resultTotalCounts.increment_failed();
207
208 // We still tear things down - but only run the teardown for each group that run
209 // setups
210 int teardownsToRun = _setupGroupsRun;
211 auto groupCount = groupStack.size();
212 auto groupsToSkip = groupCount - teardownsToRun;
213
214 // Run teardowns, but skip the first X ones, where X is the number of groups
215 // that ran setups
216 for (int i = 0; i < groupCount; i++) {
217 if (i < groupsToSkip - 1) continue;
218 auto* group = groupStack[i];
219 group->foreach_teardown(&_forEachTeardownInGroupFn);
220 }
221
222 spec->variables()->clear();
223 return;
224 }
225
226 _currentRunResultPromise = std::make_unique<std::promise<ISpecRunResult*>>();
227
228 auto* codeBlock = spec->code_block();
229 if (codeBlock->get_timeout_ms() == 0) codeBlock->set_timeout_ms(_timeoutMs);
230
231 codeBlock->run(spec, _currentGroup, spec, &_codeBlockCallbackFn);
232
233 auto future = _currentRunResultPromise->get_future();
234
235 if (codeBlock->get_timeout_ms() > 0) {
236 if (future.wait_for(std::chrono::milliseconds(codeBlock->get_timeout_ms())) ==
237 std::future_status::timeout) {
238 auto result = SpecRunResult::timeout(spec, _currentGroup, spec);
239 _reporters->report_test(result.get());
240 auto specFinalResult = SpecRunResult::failed(spec, _currentGroup, spec);
241 _reporters->report_test_result(specFinalResult.get());
242 _resultTotalCounts.increment_failed();
243 spec->variables()->clear();
244 return;
245 }
246 }
247
248 if (auto* result = future.get()) _reporters->report_test(result);
249 else _Log_("Spec callback future.get() returned nullptr");
250
251 // Run teardown blocks, starting with current and walking up to root parent
252 for (auto* group : groupStack) group->foreach_teardown(&_forEachTeardownInGroupFn);
253
254 if (_currentSpecFailed) {
255 _resultTotalCounts.increment_failed();
256 auto specFinalResult = SpecRunResult::failed(
257 spec, _currentGroup, spec, _currentSpecFailureMessage
258 );
259 _reporters->report_test_result(specFinalResult.get());
260 } else {
261 _resultTotalCounts.increment_passed();
262 auto specFinalResult = SpecRunResult::passed(spec, _currentGroup, spec);
263 _reporters->report_test_result(specFinalResult.get());
264 }
265 spec->variables()->clear();
266 }
267
268 FunctionPointer<void(ISpec*)> _forEachSpecInGroupFn{
269 this, &SpecSuiteRunInstance::foreach_test_in_group
270 };
271
272 void foreach_group_in_group(ISpecGroup* group) {
273 // TODO group these into an inline function:
274 _currentSpec = nullptr;
275 _currentSpecFailed = false;
276 _currentSpecFailureMessage.clear();
277 _currentResult = nullptr;
278
279 if (_currentlySkippingTests) {
280 _currentGroup = group;
281 group->foreach_test(&_forEachSpecInGroupFn);
282 group->foreach_group(&_forEachGroupInGroupFn);
283 return;
284 }
285
286 bool shouldRun = should_run_group(group);
287 _currentlySkippingTests = !shouldRun;
288
289 // Run the group's one time setups
290 if (!_currentlySkippingTests)
291 group->foreach_one_time_setup(&_forEachSetupInGroupFn);
292
293 _currentGroup = group;
294 group->foreach_test(&_forEachSpecInGroupFn);
295 group->foreach_group(&_forEachGroupInGroupFn);
296
297 // Run the group's one time teardowns
298 if (!_currentlySkippingTests)
299 group->foreach_one_time_teardown(&_forEachTeardownInGroupFn);
300
301 group->variables()->clear();
302
303 _currentlySkippingTests = false;
304 }
305
306 FunctionPointer<void(ISpecGroup*)> _forEachGroupInGroupFn{
307 this, &SpecSuiteRunInstance::foreach_group_in_group
308 };
309
310 void run_group(ISpecGroup* group) {
311 bool shouldRun = should_run_group(group);
312 _currentlySkippingTests = !shouldRun;
313
314 // TODO group these into an inline function:
315 _currentSpec = nullptr;
316 _currentSpecFailed = false;
317 _currentSpecFailureMessage.clear();
318 _currentResult = nullptr;
319
320 // Run the group's one time setups
321 if (!_currentlySkippingTests)
322 group->foreach_one_time_setup(&_forEachSetupInGroupFn);
323
324 // Run the specs
325 _currentGroup = group;
326 group->foreach_test(&_forEachSpecInGroupFn);
327
328 // Run any child groups
329 group->foreach_group(&_forEachGroupInGroupFn);
330
331 // Run the group's one time teardowns
332 if (!_currentlySkippingTests)
333 group->foreach_one_time_teardown(&_forEachTeardownInGroupFn);
334
335 _currentlySkippingTests = false;
336 }
337
338 public:
339 SpecSuiteRunInstance(
340 ISpecReporterCollection* reporters = nullptr, ISpecRunOptions* options = nullptr
341 )
342 : _reporters(reporters), _options(options) {}
343
344 ISpecReporterCollection* reporters() const { return _reporters; }
345
346 void run(ISpecGroup* group, ISpecSuiteRunResultCallbackFn* callback) {
347 // TODO : put all of the specs into a Queue and we can call report_suite_begin()
348 // with count
349 _reporters->report_start();
350 if (_options) _timeoutMs = _options->default_timeout_ms();
351 run_group(group);
352 _reporters->report_suite_result(&_resultTotalCounts);
353 if (callback) callback->invoke(&_resultTotalCounts);
354 }
355 };
356
357 public:
358 void run(
359 ISpecGroup* group, ISpecReporterCollection* reporters, ISpecRunOptions* options,
361 ) override {
362 SpecSuiteRunInstance(reporters, options).run(group, callback);
363 }
364 };
365}
static std::unique_ptr< SpecRunResult > timeout(ISpecComponent *component, ISpecGroup *group, ISpec *spec)
static std::unique_ptr< SpecRunResult > passed(ISpecComponent *component, ISpecGroup *group, ISpec *spec)
static std::unique_ptr< SpecRunResult > not_run(ISpecComponent *component, ISpecGroup *group, ISpec *spec)
static std::unique_ptr< SpecRunResult > failed(ISpecComponent *component, ISpecGroup *group, ISpec *spec, std::string_view message="")
void run(ISpecGroup *group, ISpecReporterCollection *reporters, ISpecRunOptions *options, ISpecSuiteRunResultCallbackFn *callback) override
Definition API.h:3
IFunctionPointer< void(ISpecSuiteRunResult *)> ISpecSuiteRunResultCallbackFn
Definition API.h:485
virtual ISpecGroup * group() const =0
virtual bool skip() const =0
virtual const char * description() const =0
virtual void foreach_one_time_teardown(ForEachTeardownFn *) const =0
virtual void foreach_group(ForEachGroupFn *) const =0
virtual void foreach_one_time_setup(ForEachSetupFn *) const =0
virtual void foreach_teardown(ForEachTeardownFn *) const =0
virtual void foreach_setup(ForEachSetupFn *) const =0
virtual void foreach_test(ForEachSpecFn *) const =0
virtual ISpecVariableCollection * variables() const =0
virtual void report_test(ISpecRunResult *)=0
virtual void report_test_begin(ISpecGroup *, ISpec *)=0
virtual void report_test_result(ISpecRunResult *)=0
virtual void report_setup(ISpecRunResult *)=0
virtual void report_teardown(ISpecRunResult *)=0
virtual void report_suite_result(ISpecSuiteRunResult *)=0
virtual std::uint32_t default_timeout_ms() const =0
virtual RunResultStatus status() const =0
virtual ISpecRunResult * copy() const =0
virtual const char * message() const =0