Specs
Beautiful C++ Test Framework
Loading...
Searching...
No Matches
SpecCodeBlock.h
Go to the documentation of this file.
1#pragma once
2
3#include <Specs/API.h>
4#include <_Log_.h>
5
6#include <future>
7#include <memory>
8#include <string>
9
10#include "GlobalSpecEnvironment.h" // <--- Global is gross, but - better way? Works for now.
11#include "SpecDone.h"
12#include "SpecRunResult.h"
13
14namespace SpecsCpp {
15
17 ISpecComponent* _component = nullptr;
18 bool _async = false;
19 std::uint32_t _timeout_ms = 0;
20
21 SpecCodeBlockBodyFn* _unmanagedBody = nullptr;
22
23 std::unique_ptr<std::promise<void>> _asyncDonePromise = nullptr;
24
25 std::unique_ptr<IFunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>
26 _managedBody = nullptr;
27
28 bool _exceptionHandled = false;
29 std::unique_ptr<std::exception_ptr> _currentExceptionPtr;
30 std::string _currentExceptionMessage;
31
32 void mark_async_promise_done() {
33 if (_asyncDonePromise) _asyncDonePromise->set_value();
34 }
35
36 FunctionPointer<void()> _markAsyncPromiseDoneFn =
37 function_pointer(this, &SpecCodeBlock::mark_async_promise_done);
38
39 void store_current_exception_message(
41 ) {
42 if (_exceptionHandled) return;
43 if (exceptionMessage)
44 if (auto* message = exceptionMessage->message())
45 _currentExceptionMessage = exceptionMessage->message();
46 }
47
49 _storeCurrentExceptionMessageFn =
50 function_pointer(this, &SpecCodeBlock::store_current_exception_message);
51
52 void foreach_exception_handler(ILocalSpecExceptionHandler* handler) {
53 if (_exceptionHandled) return;
54 if (handler->handle_exception(
55 _currentExceptionPtr.get(), &_storeCurrentExceptionMessageFn
56 ))
57 _exceptionHandled = true;
58 }
59
60 FunctionPointer<void(ILocalSpecExceptionHandler*)> _forEachExceptionHandlerFn =
61 function_pointer(this, &SpecCodeBlock::foreach_exception_handler);
62
63 public:
64 SpecCodeBlock(std::unique_ptr<
65 IFunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>
66 managedBody)
67 : _managedBody(std::move(managedBody)) {}
68
69 SpecCodeBlock(FunctionPointer<void()> body)
70 : _managedBody(
71 std::make_unique<
72 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
73 [body](
74 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
75 ) { body.invoke(); }
76 )
77 ) {}
78
79 SpecCodeBlock(FunctionPointer<void(SpecDone)> body)
80 : _managedBody(
81 std::make_unique<
82 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
83 [body](
84 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
85 ) { body.invoke(SpecDone{asyncDone}); }
86 )
87 ) {
88 mark_async();
89 }
90
91 SpecCodeBlock(FunctionPointer<void(ISpec*)> body)
92 : _managedBody(
93 std::make_unique<
94 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
95 [body](
96 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
97 ) { body.invoke(spec); }
98 )
99 ) {}
100
101 SpecCodeBlock(FunctionPointer<void(ISpecGroup*)> body)
102 : _managedBody(std::make_unique<FunctionPointer<
104 [body](ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone) {
105 // If 'self' is a group, invoke the body with it, otherwise invoke with the
106 // group() of self, if any
107 if (auto* group = dynamic_cast<ISpecGroup*>(self)) body.invoke(group);
108 else if (auto* group = self->group()) _Log_("AFTER");
109 ;
110 }
111 )) {}
112
113 SpecCodeBlock(FunctionPointer<void(ISpecGroup*, SpecDone)> body)
114 : _managedBody(
115 std::make_unique<
116 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
117 [body](
118 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
119 ) { body.invoke(self->group(), SpecDone{asyncDone}); }
120 )
121 ) {
122 mark_async();
123 }
124
125 SpecCodeBlock(FunctionPointer<void(ISpec*, SpecDone)> body)
126 : _managedBody(
127 std::make_unique<
128 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
129 [body](
130 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
131 ) { body.invoke(spec, SpecDone{asyncDone}); }
132 )
133 ) {
134 mark_async();
135 }
136
137 SpecCodeBlock(FunctionPointer<void(ISpecComponent*, ISpec*)> body)
138 : _managedBody(
139 std::make_unique<
140 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
141 [body](
142 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
143 ) { body.invoke(self, spec); }
144 )
145 ) {}
146
147 SpecCodeBlock(FunctionPointer<void(ISpecGroup*, ISpec*)> body)
148 : _managedBody(
149 std::make_unique<
150 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
151 [body](
152 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
153 ) { body.invoke(self->group(), spec); }
154 )
155 ) {}
156
157 SpecCodeBlock(FunctionPointer<void(ISpecGroup*, ISpecComponent*, ISpec*)> body)
158 : _managedBody(
159 std::make_unique<
160 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
161 [body](
162 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
163 ) { body.invoke(self->group(), self, spec); }
164 )
165 ) {}
166
167 SpecCodeBlock(FunctionPointer<void(ISpecGroup*, ISpec*, SpecDone)> body)
168 : _managedBody(
169 std::make_unique<
170 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
171 [body](
172 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
173 ) { body.invoke(self->group(), spec, SpecDone{asyncDone}); }
174 )
175 ) {
176 mark_async();
177 }
178
179 SpecCodeBlock(FunctionPointer<void(ISpecComponent*, ISpec*, SpecDone)> body)
180 : _managedBody(
181 std::make_unique<
182 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
183 [body](
184 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
185 ) { body.invoke(self, spec, SpecDone{asyncDone}); }
186 )
187 ) {
188 mark_async();
189 }
190
192 : _managedBody(
193 std::make_unique<
194 FunctionPointer<void(ISpecComponent*, ISpec*, SpecCodeBlockAsyncDoneFn*)>>(
195 [body](
196 ISpecComponent* self, ISpec* spec, SpecCodeBlockAsyncDoneFn* asyncDone
197 ) { body.invoke(self->group(), self, spec, SpecDone{asyncDone}); }
198 )
199 ) {
200 mark_async();
201 }
202
203 bool is_async() const override { return _async; }
204 void mark_async(bool async = true) override { _async = async; }
205
206 std::uint32_t get_timeout_ms() const override { return _timeout_ms; }
207 void set_timeout_ms(std::uint32_t timeout_ms) override { _timeout_ms = timeout_ms; }
208
209 SpecCodeBlockBodyFn* body() const override {
210 return _unmanagedBody ? _unmanagedBody : _managedBody.get();
211 }
212
213 void set_body(SpecCodeBlockBodyFn* body) override { _unmanagedBody = body; }
214
215 void run(
216 ISpecComponent* self, ISpecGroup* group, ISpec* spec,
217 ISpecRunResultCallbackFn* resultCallback
218 ) override {
219 try {
220 if (is_async()) {
221 _asyncDonePromise = std::make_unique<std::promise<void>>();
222 body()->invoke(self, spec, &_markAsyncPromiseDoneFn);
223
224 auto asyncDoneFuture = _asyncDonePromise->get_future();
225 if (get_timeout_ms() > 0) {
226 if (asyncDoneFuture.wait_for(std::chrono::milliseconds(get_timeout_ms())) ==
227 std::future_status::timeout) {
228 if (resultCallback) {
229 auto result = SpecRunResult::timeout(self, group, spec);
230 resultCallback->invoke(result.get());
231 return;
232 } else {
233 _Log_("Code block timed out");
234 }
235 }
236 }
237
238 asyncDoneFuture.get();
239
240 } else {
241 body()->invoke(self, spec, nullptr);
242 }
243
244 if (!resultCallback) return;
245 auto result = SpecRunResult::passed(self, group, spec);
246 resultCallback->invoke(result.get());
247 } catch (...) {
248 _exceptionHandled = false;
249
250 // ...
251 if (_asyncDonePromise) _asyncDonePromise->set_exception(std::current_exception());
252
253 if (auto* currentlyRunningSpecEnvironment = global_spec_environment().get()) {
254 if (auto* exceptionHandlers =
255 currentlyRunningSpecEnvironment->local_exception_handlers()) {
256 _currentExceptionMessage.clear();
257 _currentExceptionPtr =
258 std::make_unique<std::exception_ptr>(std::current_exception());
259 exceptionHandlers->foreach_exception_handler(&_forEachExceptionHandlerFn);
260 if (_exceptionHandled) {
261 if (resultCallback) {
262 auto result = SpecRunResult::failed(
263 self, group, spec, _currentExceptionMessage.c_str()
264 );
265 resultCallback->invoke(result.get());
266 } else {
267 _Log_("Code block error: {}", _currentExceptionMessage);
268 }
269 } else {
270 if (resultCallback) {
271 auto result =
272 SpecRunResult::failed(self, group, spec, "Unknown exception");
273 resultCallback->invoke(result.get());
274
275 } else {
276 _Log_("Code block error: Unhandled exception");
277 }
278 }
279 } else {
280 _Log_("No exception handlers registered");
281 }
282 } else {
283 _Log_("No currently running spec environment");
284 }
285 }
286 }
287 };
288}
void set_body(SpecCodeBlockBodyFn *body) override
SpecCodeBlock(std::unique_ptr< IFunctionPointer< void(ISpecComponent *, ISpec *, SpecCodeBlockAsyncDoneFn *)> > managedBody)
SpecCodeBlock(FunctionPointer< void()> body)
SpecCodeBlock(FunctionPointer< void(ISpecGroup *, ISpecComponent *, ISpec *)> body)
SpecCodeBlock(FunctionPointer< void(ISpecGroup *, SpecDone)> body)
void run(ISpecComponent *self, ISpecGroup *group, ISpec *spec, ISpecRunResultCallbackFn *resultCallback) override
SpecCodeBlock(FunctionPointer< void(SpecDone)> body)
SpecCodeBlock(FunctionPointer< void(ISpec *, SpecDone)> body)
SpecCodeBlock(FunctionPointer< void(ISpecComponent *, ISpec *, SpecDone)> body)
SpecCodeBlock(FunctionPointer< void(ISpecGroup *, ISpec *)> body)
SpecCodeBlock(FunctionPointer< void(ISpecComponent *, ISpec *)> body)
void mark_async(bool async=true) override
void set_timeout_ms(std::uint32_t timeout_ms) override
SpecCodeBlock(FunctionPointer< void(ISpecGroup *, ISpec *, SpecDone)> body)
std::uint32_t get_timeout_ms() const override
bool is_async() const override
SpecCodeBlock(FunctionPointer< void(ISpecGroup *, ISpecComponent *, ISpec *, SpecDone)> body)
SpecCodeBlockBodyFn * body() const override
SpecCodeBlock(FunctionPointer< void(ISpec *)> body)
SpecCodeBlock(FunctionPointer< void(ISpecGroup *)> body)
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 > failed(ISpecComponent *component, ISpecGroup *group, ISpec *spec, std::string_view message="")
Definition API.h:3
GlobalSpecEnvironment & global_spec_environment()
IFunctionPointer< void(ISpecRunResult *)> ISpecRunResultCallbackFn
Definition API.h:198
virtual bool handle_exception(std::exception_ptr *exception, LocalSpecExceptionFailureMessageCallbackFn *failureMessageCallback)=0
IFunctionPointer< void(ISpecComponent *, ISpec *, SpecCodeBlockAsyncDoneFn *)> SpecCodeBlockBodyFn
Definition API.h:203
IFunctionPointer< void()> SpecCodeBlockAsyncDoneFn
Definition API.h:201