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.
5 // Copyright (C) 2019 Intel Corporation
8 #include "test_precomp.hpp"
9 #include "opencv2/gapi/gcomputation_async.hpp"
10 #include "opencv2/gapi/gcompiled_async.hpp"
12 #include <condition_variable>
17 //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).
18 //So these differences are factored into devoted helper classes (mixins) which are then used by the common test script by help of CRTP.
19 //Actual GAPI Computation with parameters to run on is mixed into test via CRTP as well.
22 cv::GComputation sum_of_sum;
23 SumOfSum2x2() : sum_of_sum([]{
25 cv::GScalar out = cv::gapi::sum(in + in);
26 return GComputation{in, out};
30 const cv::Size sz{2, 2};
31 cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
34 cv::GCompiled compile(){
35 return sum_of_sum.compile(descr_of(in_mat));
38 cv::GComputation& computation(){
42 cv::GCompileArgs compile_args(){
46 cv::GRunArgs in_args(){
47 return cv::gin(in_mat);
50 cv::GRunArgsP out_args(){
51 return cv::gout(out_sc);
55 EXPECT_EQ(8, out_sc[0]);
60 G_TYPED_KERNEL(GThrow, <GMat(GMat)>, "org.opencv.test.throw")
62 static GMatDesc outMeta(GMatDesc in) { return in; }
66 struct gthrow_exception : std::runtime_error {
67 using std::runtime_error::runtime_error;
70 GAPI_OCV_KERNEL(GThrowImpl, GThrow)
72 static void run(const cv::Mat& in, cv::Mat&)
74 //this condition is needed to avoid "Unreachable code" warning on windows inside OCVCallHelper
77 throw gthrow_exception{"test"};
83 struct ExceptionOnExecution {
84 cv::GComputation throwing_gcomp;
85 ExceptionOnExecution() : throwing_gcomp([]{
87 auto gout = GThrow::on(in);
88 return GComputation{in, gout};
93 const cv::Size sz{2, 2};
94 cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
97 cv::GCompiled compile(){
98 return throwing_gcomp.compile(descr_of(in_mat), compile_args());
101 cv::GComputation& computation(){
102 return throwing_gcomp;
105 cv::GRunArgs in_args(){
106 return cv::gin(in_mat);
109 cv::GRunArgsP out_args(){
110 return cv::gout(out);
113 cv::GCompileArgs compile_args(){
114 auto pkg = cv::gapi::kernels<GThrowImpl>();
115 return cv::compile_args(pkg);
120 template<typename crtp_final_t>
122 template<typename crtp_base_t>
123 static crtp_final_t* crtp_cast_(crtp_base_t* this_)
125 return static_cast<crtp_final_t*>(this_);
129 //Test Mixin, hiding details of callback based notification
130 template<typename crtp_final_t>
131 struct CallBack: crtp_cast<crtp_final_t> {
132 std::atomic<bool> callback_called = {false};
134 std::exception_ptr ep;
136 std::condition_variable cv;
138 std::function<void(std::exception_ptr)> callback(){
139 return [&](std::exception_ptr ep_){
141 callback_called = true;
148 template<typename... Args >
149 void start_async(Args&&... args){
150 this->crtp_cast_(this)->async(callback(), std::forward<Args>(args)...);
153 void wait_for_result()
155 std::unique_lock<std::mutex> lck{mtx};
156 cv.wait(lck,[&]{return callback_called == true;});
159 std::rethrow_exception(ep);
164 //Test Mixin, hiding details of future based notification
165 template<typename crtp_final_t>
166 struct Future: crtp_cast<crtp_final_t> {
169 template<typename... Args >
170 void start_async(Args&&... args){
171 f = this->crtp_cast_(this)->async(std::forward<Args>(args)...);
174 void wait_for_result()
180 //Test Mixin, hiding details of using compiled GAPI object
181 template<typename crtp_final_t>
182 struct AsyncCompiled : crtp_cast<crtp_final_t>{
184 template<typename... Args>
185 auto async(Args&&... args) -> decltype(cv::gapi::wip::async(std::declval<cv::GCompiled&>(), std::forward<Args>(args)...)){
186 auto gcmpld = this->crtp_cast_(this)->compile();
187 return cv::gapi::wip::async(gcmpld, std::forward<Args>(args)...);
191 //Test Mixin, hiding details of calling apply (async_apply) on GAPI Computation object
192 template<typename crtp_final_t>
193 struct AsyncApply : crtp_cast<crtp_final_t> {
195 template<typename... Args>
196 auto async(Args&&... args) ->decltype(cv::gapi::wip::async_apply(std::declval<cv::GComputation&>(), std::forward<Args>(args)...)) {
197 return cv::gapi::wip::async_apply(this->crtp_cast_(this)->computation(), std::forward<Args>(args)..., this->crtp_cast_(this)->compile_args());
202 template<typename case_t>
203 struct normal: ::testing::Test, case_t{};
205 TYPED_TEST_CASE_P(normal);
207 TYPED_TEST_P(normal, basic){
208 //Normal scenario: start function asynchronously and wait for the result, and verify it
209 this->start_async(this->in_args(), this->out_args());
210 this->wait_for_result();
215 REGISTER_TYPED_TEST_CASE_P(normal,
219 template<typename case_t>
220 struct exception: ::testing::Test, case_t{};
221 TYPED_TEST_CASE_P(exception);
223 TYPED_TEST_P(exception, basic){
224 //Exceptional scenario: start function asynchronously and make sure exception is passed to the user
225 this->start_async(this->in_args(), this->out_args());
226 EXPECT_THROW(this->wait_for_result(), gthrow_exception);
229 REGISTER_TYPED_TEST_CASE_P(exception,
233 template<typename case_t>
234 struct stress : ::testing::Test{};
235 TYPED_TEST_CASE_P(stress);
237 TYPED_TEST_P(stress, test){
238 //Some stress testing: use a number of threads to start a bunch of async requests
239 const std::size_t request_per_thread = 10;
240 const std::size_t number_of_threads = 4;
242 auto thread_body = [&](){
243 std::vector<TypeParam> requests{request_per_thread};
244 for (auto&& r : requests){
245 r.start_async(r.in_args(), r.out_args());
248 for (auto&& r : requests){
254 std::vector<std::thread> pool {number_of_threads};
255 for (auto&& t : pool){
256 t = std::thread{thread_body};
259 for (auto&& t : pool){
263 REGISTER_TYPED_TEST_CASE_P(stress, test);
265 //little helpers to match up all combinations of setups
266 template<typename compute_fixture_t,template <typename> class callback_or_future_t, template <typename> class compiled_or_apply_t>
269 callback_or_future_t<Case<compute_fixture_t,callback_or_future_t,compiled_or_apply_t>>,
270 compiled_or_apply_t <Case<compute_fixture_t,callback_or_future_t,compiled_or_apply_t>>
273 template<typename computation_t>
274 using cases = ::testing::Types<
275 Case<computation_t, CallBack, AsyncCompiled>,
276 Case<computation_t, CallBack, AsyncApply>,
277 Case<computation_t, Future, AsyncCompiled>,
278 Case<computation_t, Future, AsyncApply>
280 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPINormalFlow_, normal, cases<SumOfSum2x2>);
281 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIExceptionHandling_, exception, cases<ExceptionOnExecution>);
283 INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIStress, stress, cases<SumOfSum2x2>);
285 TEST(AsyncAPI, Sample){
286 cv::GComputation self_mul([]{
288 cv::GMat out = cv::gapi::mul(in, in);
289 return GComputation{in, out};
292 const cv::Size sz{2, 2};
293 cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
296 auto f = cv::gapi::wip::async_apply(self_mul,cv::gin(in_mat), cv::gout(out));
299 } // namespace opencv_test