1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
4 //
5 // Copyright (C) 2019 Intel Corporation
6
7
8 #include "test_precomp.hpp"
9 #include <opencv2/gapi/gcomputation_async.hpp>
10 #include <opencv2/gapi/gcompiled_async.hpp>
11 #include <opencv2/gapi/gasync_context.hpp>
12
13
14 #include <condition_variable>
15 #include <stdexcept>
16
17 namespace opencv_test
18 {
19 //Main idea behind these tests is to have the same test script that is parameterized in order to test all setups (GCompiled vs apply, callback vs future).
20 //So these differences are factored into devoted helper classes (mixins) which are then used by the common test script by help of CRTP.
21 //Actual GAPI Computation with parameters to run on is mixed into test via CRTP as well.
22
23 struct SumOfSum2x2 {
24 cv::GComputation sum_of_sum;
SumOfSum2x2opencv_test::SumOfSum2x225 SumOfSum2x2() : sum_of_sum([]{
26 cv::GMat in;
27 cv::GScalar out = cv::gapi::sum(in + in);
28 return GComputation{in, out};
29 })
30 {}
31
32 const cv::Size sz{2, 2};
33 cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
34 cv::Scalar out_sc;
35
compileopencv_test::SumOfSum2x236 cv::GCompiled compile(){
37 return sum_of_sum.compile(descr_of(in_mat));
38 }
39
computationopencv_test::SumOfSum2x240 cv::GComputation& computation(){
41 return sum_of_sum;
42 }
43
compile_argsopencv_test::SumOfSum2x244 cv::GCompileArgs compile_args(){
45 return {};
46 }
47
in_argsopencv_test::SumOfSum2x248 cv::GRunArgs in_args(){
49 return cv::gin(in_mat);
50 }
51
out_argsopencv_test::SumOfSum2x252 cv::GRunArgsP out_args(){
53 return cv::gout(out_sc);
54 }
55
verifyopencv_test::SumOfSum2x256 void verify(){
57 EXPECT_EQ(8, out_sc[0]);
58 }
59 };
60
61 namespace {
62 G_TYPED_KERNEL(GThrow, <GMat(GMat)>, "org.opencv.test.throw")
63 {
outMeta(GMatDesc in)64 static GMatDesc outMeta(GMatDesc in) { return in; }
65
66 };
67
68 struct gthrow_exception : std::runtime_error {
69 using std::runtime_error::runtime_error;
70 };
71
GAPI_OCV_KERNEL(GThrowImpl,GThrow)72 GAPI_OCV_KERNEL(GThrowImpl, GThrow)
73 {
74 static void run(const cv::Mat& in, cv::Mat&)
75 {
76 //this condition is needed to avoid "Unreachable code" warning on windows inside OCVCallHelper
77 if (!in.empty())
78 {
79 throw gthrow_exception{"test"};
80 }
81 }
82 };
83
84
85 //TODO: unify with callback helper code
86 struct cancel_struct {
87 std::atomic<int> num_tasks_to_spawn;
88
89 cv::gapi::wip::GAsyncContext ctx;
90
cancel_structopencv_test::__anon0e4d59180211::cancel_struct91 cancel_struct(int tasks_to_spawn) : num_tasks_to_spawn(tasks_to_spawn) {}
92 };
93
94 G_TYPED_KERNEL(GCancelationAdHoc, <GMat(GMat, cancel_struct*)>, "org.opencv.test.cancel_ad_hoc")
95 {
outMeta(GMatDesc in,cancel_struct *)96 static GMatDesc outMeta(GMatDesc in, cancel_struct* ) { return in; }
97
98 };
99
GAPI_OCV_KERNEL(GCancelationAdHocImpl,GCancelationAdHoc)100 GAPI_OCV_KERNEL(GCancelationAdHocImpl, GCancelationAdHoc)
101 {
102 static void run(const cv::Mat& , cancel_struct* cancel_struct_p, cv::Mat&) {
103 auto& cancel_struct_ = * cancel_struct_p;
104 auto num_tasks_to_spawn = -- cancel_struct_.num_tasks_to_spawn;
105 cancel_struct_.ctx.cancel();
106 EXPECT_GT(num_tasks_to_spawn, 0)<<"Incorrect Test setup - to small number of tasks to feed the queue \n";
107 }
108 };
109 }
110
111 struct ExceptionOnExecution {
112 cv::GComputation throwing_gcomp;
ExceptionOnExecutionopencv_test::ExceptionOnExecution113 ExceptionOnExecution() : throwing_gcomp([]{
114 cv::GMat in;
115 auto gout = GThrow::on(in);
116 return GComputation{in, gout};
117 })
118 {}
119
120
121 const cv::Size sz{2, 2};
122 cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
123 cv::Mat out;
124
compileopencv_test::ExceptionOnExecution125 cv::GCompiled compile(){
126 return throwing_gcomp.compile(descr_of(in_mat), compile_args());
127 }
128
computationopencv_test::ExceptionOnExecution129 cv::GComputation& computation(){
130 return throwing_gcomp;
131 }
132
in_argsopencv_test::ExceptionOnExecution133 cv::GRunArgs in_args(){
134 return cv::gin(in_mat);
135 }
136
out_argsopencv_test::ExceptionOnExecution137 cv::GRunArgsP out_args(){
138 return cv::gout(out);
139 }
140
compile_argsopencv_test::ExceptionOnExecution141 cv::GCompileArgs compile_args(){
142 auto pkg = cv::gapi::kernels<GThrowImpl>();
143 return cv::compile_args(pkg);
144 }
145
146 };
147
148 struct SelfCanceling {
149 cv::GComputation self_cancel;
SelfCancelingopencv_test::SelfCanceling150 SelfCanceling(cancel_struct* cancel_struct_p) : self_cancel([cancel_struct_p]{
151 cv::GMat in;
152 cv::GMat out = GCancelationAdHoc::on(in, cancel_struct_p);
153 return GComputation{in, out};
154 })
155 {}
156
157 const cv::Size sz{2, 2};
158 cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
159 cv::Mat out_mat;
160
compileopencv_test::SelfCanceling161 cv::GCompiled compile(){
162 return self_cancel.compile(descr_of(in_mat), compile_args());
163 }
164
computationopencv_test::SelfCanceling165 cv::GComputation& computation(){
166 return self_cancel;
167 }
168
in_argsopencv_test::SelfCanceling169 cv::GRunArgs in_args(){
170 return cv::gin(in_mat);
171 }
172
out_argsopencv_test::SelfCanceling173 cv::GRunArgsP out_args(){
174 return cv::gout(out_mat);
175 }
176
compile_argsopencv_test::SelfCanceling177 cv::GCompileArgs compile_args(){
178 auto pkg = cv::gapi::kernels<GCancelationAdHocImpl>();
179 return cv::compile_args(pkg);
180 }
181 };
182
183 template<typename crtp_final_t>
184 struct crtp_cast {
185 template<typename crtp_base_t>
crtp_cast_opencv_test::crtp_cast186 static crtp_final_t* crtp_cast_(crtp_base_t* this_)
187 {
188 return static_cast<crtp_final_t*>(this_);
189 }
190 };
191
192 //Test Mixin, hiding details of callback based notification
193 template<typename crtp_final_t>
194 struct CallBack: crtp_cast<crtp_final_t> {
195 std::atomic<bool> callback_called = {false};
196 std::mutex mtx;
197 std::exception_ptr ep;
198
199 std::condition_variable cv;
200
callbackopencv_test::CallBack201 std::function<void(std::exception_ptr)> callback(){
202 return [&](std::exception_ptr ep_){
203 ep = ep_;
204 callback_called = true;
205 mtx.lock();
206 mtx.unlock();
207 cv.notify_one();
208 };
209 };
210
211 template<typename... Args >
start_asyncopencv_test::CallBack212 void start_async(Args&&... args){
213 this->crtp_cast_(this)->async(callback(), std::forward<Args>(args)...);
214 }
215
216 template<typename... Args >
start_asyncopencv_test::CallBack217 void start_async(cv::gapi::wip::GAsyncContext& ctx, Args&&... args){
218 this->crtp_cast_(this)->async(ctx, callback(), std::forward<Args>(args)...);
219 }
220
wait_for_resultopencv_test::CallBack221 void wait_for_result()
222 {
223 std::unique_lock<std::mutex> lck{mtx};
224 cv.wait(lck,[&]{return callback_called == true;});
225 if (ep)
226 {
227 std::rethrow_exception(ep);
228 }
229 }
230 };
231
232 //Test Mixin, hiding details of future based notification
233 template<typename crtp_final_t>
234 struct Future: crtp_cast<crtp_final_t> {
235 std::future<void> f;
236
237 template<typename... Args >
start_asyncopencv_test::Future238 void start_async(Args&&... args){
239 f = this->crtp_cast_(this)->async(std::forward<Args>(args)...);
240 }
241
wait_for_resultopencv_test::Future242 void wait_for_result()
243 {
244 f.get();
245 }
246 };
247
248 //Test Mixin, hiding details of using compiled GAPI object
249 template<typename crtp_final_t>
250 struct AsyncCompiled : crtp_cast<crtp_final_t>{
251
252 template<typename... Args>
asyncopencv_test::AsyncCompiled253 auto async(Args&&... args) -> decltype(cv::gapi::wip::async(std::declval<cv::GCompiled&>(), std::forward<Args>(args)...)){
254 auto gcmpld = this->crtp_cast_(this)->compile();
255 return cv::gapi::wip::async(gcmpld, std::forward<Args>(args)...);
256 }
257
258 template<typename... Args>
asyncopencv_test::AsyncCompiled259 auto async(cv::gapi::wip::GAsyncContext& ctx, Args&&... args) ->
260 decltype(cv::gapi::wip::async(std::declval<cv::GCompiled&>(), std::forward<Args>(args)..., std::declval<cv::gapi::wip::GAsyncContext&>()))
261 {
262 auto gcmpld = this->crtp_cast_(this)->compile();
263 return cv::gapi::wip::async(gcmpld, std::forward<Args>(args)..., ctx);
264 }
265 };
266
267 //Test Mixin, hiding details of calling apply (async_apply) on GAPI Computation object
268 template<typename crtp_final_t>
269 struct AsyncApply : crtp_cast<crtp_final_t> {
270
271 template<typename... Args>
asyncopencv_test::AsyncApply272 auto async(Args&&... args) ->
273 decltype(cv::gapi::wip::async_apply(std::declval<cv::GComputation&>(), std::forward<Args>(args)..., std::declval<cv::GCompileArgs>()))
274 {
275 return cv::gapi::wip::async_apply(
276 this->crtp_cast_(this)->computation(), std::forward<Args>(args)..., this->crtp_cast_(this)->compile_args()
277 );
278 }
279
280 template<typename... Args>
asyncopencv_test::AsyncApply281 auto async(cv::gapi::wip::GAsyncContext& ctx, Args&&... args) ->
282 decltype(cv::gapi::wip::async_apply(std::declval<cv::GComputation&>(), std::forward<Args>(args)... , std::declval<cv::GCompileArgs>(), std::declval<cv::gapi::wip::GAsyncContext&>()))
283 {
284 return cv::gapi::wip::async_apply(
285 this->crtp_cast_(this)->computation(), std::forward<Args>(args)..., this->crtp_cast_(this)->compile_args(), ctx
286 );
287 }
288
289 };
290
291
292 template<typename case_t>
293 struct normal: ::testing::Test, case_t{};
294
295 TYPED_TEST_CASE_P(normal);
296
TYPED_TEST_P(normal,basic)297 TYPED_TEST_P(normal, basic){
298 //Normal scenario: start function asynchronously and wait for the result, and verify it
299 this->start_async(this->in_args(), this->out_args());
300 this->wait_for_result();
301
302 this->verify();
303 }
304
305 REGISTER_TYPED_TEST_CASE_P(normal,
306 basic
307 );
308
309 template<typename case_t>
310 struct exception: ::testing::Test, case_t{};
311 TYPED_TEST_CASE_P(exception);
312
TYPED_TEST_P(exception,basic)313 TYPED_TEST_P(exception, basic){
314 //Exceptional scenario: start function asynchronously and make sure exception is passed to the user
315 this->start_async(this->in_args(), this->out_args());
316 EXPECT_THROW(this->wait_for_result(), gthrow_exception);
317 }
318
319 REGISTER_TYPED_TEST_CASE_P(exception,
320 basic
321 );
322
323 template<typename case_t>
324 struct stress : ::testing::Test{};
325 TYPED_TEST_CASE_P(stress);
326
TYPED_TEST_P(stress,test)327 TYPED_TEST_P(stress, test){
328 //Some stress testing: use a number of threads to start a bunch of async requests
329 const std::size_t request_per_thread = 10;
330 const std::size_t number_of_threads = 4;
331
332 auto thread_body = [&](){
333 std::vector<TypeParam> requests(request_per_thread);
334 for (auto&& r : requests){
335 r.start_async(r.in_args(), r.out_args());
336 }
337
338 for (auto&& r : requests){
339 r.wait_for_result();
340 r.verify();
341 }
342 };
343
344 std::vector<std::thread> pool {number_of_threads};
345 for (auto&& t : pool){
346 t = std::thread{thread_body};
347 }
348
349 for (auto&& t : pool){
350 t.join();
351 }
352 }
353 REGISTER_TYPED_TEST_CASE_P(stress, test);
354
355 template<typename case_t>
356 struct cancel : ::testing::Test{};
357 TYPED_TEST_CASE_P(cancel);
358
TYPED_TEST_P(cancel,basic)359 TYPED_TEST_P(cancel, basic)
360 {
361 #if defined(__GNUC__) && __GNUC__ >= 11
362 // std::vector<TypeParam> requests can't handle type with ctor parameter (SelfCanceling)
363 FAIL() << "Test code is not available due to compilation error with GCC 11";
364 #else
365 constexpr int num_tasks = 100;
366 cancel_struct cancel_struct_ {num_tasks};
367 std::vector<TypeParam> requests; requests.reserve(num_tasks);
368
369 for (auto i = num_tasks; i>0; i--){
370 requests.emplace_back(&cancel_struct_);
371 }
372 for (auto&& r : requests){
373 //first request will cancel other on it's execution
374 r.start_async(cancel_struct_.ctx, r.in_args(), r.out_args());
375 }
376
377 unsigned int canceled = 0 ;
378 for (auto&& r : requests){
379 try {
380 r.wait_for_result();
381 }catch (cv::gapi::wip::GAsyncCanceled&){
382 ++canceled;
383 }
384 }
385 ASSERT_GT(canceled, 0u);
386 #endif
387 }
388
389 namespace {
deep_copy_out_args(const GRunArgsP & args)390 GRunArgs deep_copy_out_args(const GRunArgsP& args ){
391 GRunArgs result; result.reserve(args.size());
392 for (auto&& arg : args){
393 //FIXME: replace this switch with use of visit() on variant, when it will be available
394 switch (arg.index()){
395 case GRunArgP::index_of<cv::UMat*>() : result.emplace_back(*util::get<cv::UMat*>(arg)); break;
396 case GRunArgP::index_of<cv::Mat*>() : result.emplace_back(*util::get<cv::Mat*>(arg)); break;
397 case GRunArgP::index_of<cv::Scalar*>() : result.emplace_back(*util::get<cv::Scalar*> (arg)); break;
398 case GRunArgP::index_of<cv::detail::VectorRef>() : result.emplace_back(util::get<cv::detail::VectorRef> (arg)); break;
399 default : ;
400 }
401 }
402 return result;
403 }
404
args_p_from_args(GRunArgs & args)405 GRunArgsP args_p_from_args(GRunArgs& args){
406 GRunArgsP result; result.reserve(args.size());
407 for (auto&& arg : args){
408 switch (arg.index()){
409 case GRunArg::index_of<cv::Mat>() : result.emplace_back(&util::get<cv::Mat>(arg)); break;
410 case GRunArg::index_of<cv::UMat>() : result.emplace_back(&util::get<cv::UMat>(arg)); break;
411 case GRunArg::index_of<cv::Scalar>() : result.emplace_back(&util::get<cv::Scalar> (arg)); break;
412 case GRunArg::index_of<cv::detail::VectorRef>() : result.emplace_back(util::get<cv::detail::VectorRef> (arg)); break;
413 default : ;
414 }
415 }
416 return result;
417 }
418 }
419
420 REGISTER_TYPED_TEST_CASE_P(cancel, basic);
421
422 template<typename case_t>
423 struct output_args_lifetime : ::testing::Test{
424 static constexpr const int num_of_requests = 20;
425 };
426 TYPED_TEST_CASE_P(output_args_lifetime);
427 //There are intentionally no actual checks (asserts and verify) in output_args_lifetime tests.
428 //They are more of example use-cases than real tests. (ASAN/valgrind can still catch issues here)
TYPED_TEST_P(output_args_lifetime,callback)429 TYPED_TEST_P(output_args_lifetime, callback){
430
431 std::atomic<int> active_requests = {0};
432
433 for (int i=0; i<this->num_of_requests; i++)
434 {
435 TypeParam r;
436
437 //As output arguments are __captured by reference__ calling code
438 //__must__ ensure they live long enough to complete asynchronous activity.
439 //(i.e. live at least until callback is called)
440 auto out_args_ptr = std::make_shared<cv::GRunArgs>(deep_copy_out_args(r.out_args()));
441
442 //Extend lifetime of out_args_ptr content by capturing it into a callback
443 auto cb = [&active_requests, out_args_ptr](std::exception_ptr ){
444 --active_requests;
445 };
446
447 ++active_requests;
448
449 r.async(cb, r.in_args(), args_p_from_args(*out_args_ptr));
450 }
451
452
453 while(active_requests){
454 std::this_thread::sleep_for(std::chrono::milliseconds{2});
455 }
456 }
457
458
TYPED_TEST_P(output_args_lifetime,future)459 TYPED_TEST_P(output_args_lifetime, future){
460
461 std::vector<std::future<void>> fs(this->num_of_requests);
462 std::vector<std::shared_ptr<cv::GRunArgs>> out_ptrs(this->num_of_requests);
463
464 for (int i=0; i<this->num_of_requests; i++)
465 {
466 TypeParam r;
467
468 //As output arguments are __captured by reference__ calling code
469 //__must__ ensure they live long enough to complete asynchronous activity.
470 //(i.e. live at least until future.get()/wait() is returned)
471 auto out_args_ptr = std::make_shared<cv::GRunArgs>(deep_copy_out_args(r.out_args()));
472
473 //Extend lifetime of out_args_ptr content
474 out_ptrs[i] = out_args_ptr;
475
476 fs[i] = r.async(r.in_args(), args_p_from_args(*out_args_ptr));
477 }
478
479 for (auto const& ftr : fs ){
480 ftr.wait();
481 }
482 }
483 REGISTER_TYPED_TEST_CASE_P(output_args_lifetime, callback, future);
484
485 //little helpers to match up all combinations of setups
486 template<typename compute_fixture_t, template<typename> class... args_t>
487 struct Case
488 : compute_fixture_t,
489 args_t<Case<compute_fixture_t, args_t...>> ...
490 {
491 template<typename... Args>
Caseopencv_test::Case492 Case(Args&&... args) : compute_fixture_t(std::forward<Args>(args)...) { }
493 Case(Case const & ) = default;
494 Case(Case && ) = default;
495
496 Case() = default;
497 };
498
499 template<typename computation_t>
500 using cases = ::testing::Types<
501 Case<computation_t, CallBack, AsyncCompiled>,
502 Case<computation_t, CallBack, AsyncApply>,
503 Case<computation_t, Future, AsyncCompiled>,
504 Case<computation_t, Future, AsyncApply>
505 >;
506
507 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPINormalFlow_, normal, cases<SumOfSum2x2>);
508 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIExceptionHandling_, exception, cases<ExceptionOnExecution>);
509
510 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIStress, stress, cases<SumOfSum2x2>);
511
512 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPICancelation, cancel, cases<SelfCanceling>);
513
514 template<typename computation_t>
515 using explicit_wait_cases = ::testing::Types<
516 Case<computation_t, AsyncCompiled>,
517 Case<computation_t, AsyncApply>,
518 Case<computation_t, AsyncCompiled>,
519 Case<computation_t, AsyncApply>
520 >;
521
522 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIOutArgsLifetTime, output_args_lifetime, explicit_wait_cases<SumOfSum2x2>);
523
524 } // namespace opencv_test
525