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