123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- // This file is part of OpenCV project.
- // It is subject to the license terms in the LICENSE file found in the top-level directory
- // of this distribution and at http://opencv.org/license.html.
- //
- // Copyright (C) 2019 Intel Corporation
- #include "test_precomp.hpp"
- #include <opencv2/gapi/gcomputation_async.hpp>
- #include <opencv2/gapi/gcompiled_async.hpp>
- #include <opencv2/gapi/gasync_context.hpp>
- #include <condition_variable>
- #include <stdexcept>
- namespace opencv_test
- {
- //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).
- //So these differences are factored into devoted helper classes (mixins) which are then used by the common test script by help of CRTP.
- //Actual GAPI Computation with parameters to run on is mixed into test via CRTP as well.
- struct SumOfSum2x2 {
- cv::GComputation sum_of_sum;
- SumOfSum2x2() : sum_of_sum([]{
- cv::GMat in;
- cv::GScalar out = cv::gapi::sum(in + in);
- return GComputation{in, out};
- })
- {}
- const cv::Size sz{2, 2};
- cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
- cv::Scalar out_sc;
- cv::GCompiled compile(){
- return sum_of_sum.compile(descr_of(in_mat));
- }
- cv::GComputation& computation(){
- return sum_of_sum;
- }
- cv::GCompileArgs compile_args(){
- return {};
- }
- cv::GRunArgs in_args(){
- return cv::gin(in_mat);
- }
- cv::GRunArgsP out_args(){
- return cv::gout(out_sc);
- }
- void verify(){
- EXPECT_EQ(8, out_sc[0]);
- }
- };
- namespace {
- G_TYPED_KERNEL(GThrow, <GMat(GMat)>, "org.opencv.test.throw")
- {
- static GMatDesc outMeta(GMatDesc in) { return in; }
- };
- struct gthrow_exception : std::runtime_error {
- using std::runtime_error::runtime_error;
- };
- GAPI_OCV_KERNEL(GThrowImpl, GThrow)
- {
- static void run(const cv::Mat& in, cv::Mat&)
- {
- //this condition is needed to avoid "Unreachable code" warning on windows inside OCVCallHelper
- if (!in.empty())
- {
- throw gthrow_exception{"test"};
- }
- }
- };
- //TODO: unify with callback helper code
- struct cancel_struct {
- std::atomic<int> num_tasks_to_spawn;
- cv::gapi::wip::GAsyncContext ctx;
- cancel_struct(int tasks_to_spawn) : num_tasks_to_spawn(tasks_to_spawn) {}
- };
- G_TYPED_KERNEL(GCancelationAdHoc, <GMat(GMat, cancel_struct*)>, "org.opencv.test.cancel_ad_hoc")
- {
- static GMatDesc outMeta(GMatDesc in, cancel_struct* ) { return in; }
- };
- GAPI_OCV_KERNEL(GCancelationAdHocImpl, GCancelationAdHoc)
- {
- static void run(const cv::Mat& , cancel_struct* cancel_struct_p, cv::Mat&) {
- auto& cancel_struct_ = * cancel_struct_p;
- auto num_tasks_to_spawn = -- cancel_struct_.num_tasks_to_spawn;
- cancel_struct_.ctx.cancel();
- EXPECT_GT(num_tasks_to_spawn, 0)<<"Incorrect Test setup - to small number of tasks to feed the queue \n";
- }
- };
- }
- struct ExceptionOnExecution {
- cv::GComputation throwing_gcomp;
- ExceptionOnExecution() : throwing_gcomp([]{
- cv::GMat in;
- auto gout = GThrow::on(in);
- return GComputation{in, gout};
- })
- {}
- const cv::Size sz{2, 2};
- cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
- cv::Mat out;
- cv::GCompiled compile(){
- return throwing_gcomp.compile(descr_of(in_mat), compile_args());
- }
- cv::GComputation& computation(){
- return throwing_gcomp;
- }
- cv::GRunArgs in_args(){
- return cv::gin(in_mat);
- }
- cv::GRunArgsP out_args(){
- return cv::gout(out);
- }
- cv::GCompileArgs compile_args(){
- auto pkg = cv::gapi::kernels<GThrowImpl>();
- return cv::compile_args(pkg);
- }
- };
- struct SelfCanceling {
- cv::GComputation self_cancel;
- SelfCanceling(cancel_struct* cancel_struct_p) : self_cancel([cancel_struct_p]{
- cv::GMat in;
- cv::GMat out = GCancelationAdHoc::on(in, cancel_struct_p);
- return GComputation{in, out};
- })
- {}
- const cv::Size sz{2, 2};
- cv::Mat in_mat{sz, CV_8U, cv::Scalar(1)};
- cv::Mat out_mat;
- cv::GCompiled compile(){
- return self_cancel.compile(descr_of(in_mat), compile_args());
- }
- cv::GComputation& computation(){
- return self_cancel;
- }
- cv::GRunArgs in_args(){
- return cv::gin(in_mat);
- }
- cv::GRunArgsP out_args(){
- return cv::gout(out_mat);
- }
- cv::GCompileArgs compile_args(){
- auto pkg = cv::gapi::kernels<GCancelationAdHocImpl>();
- return cv::compile_args(pkg);
- }
- };
- template<typename crtp_final_t>
- struct crtp_cast {
- template<typename crtp_base_t>
- static crtp_final_t* crtp_cast_(crtp_base_t* this_)
- {
- return static_cast<crtp_final_t*>(this_);
- }
- };
- //Test Mixin, hiding details of callback based notification
- template<typename crtp_final_t>
- struct CallBack: crtp_cast<crtp_final_t> {
- std::atomic<bool> callback_called = {false};
- std::mutex mtx;
- std::exception_ptr ep;
- std::condition_variable cv;
- std::function<void(std::exception_ptr)> callback(){
- return [&](std::exception_ptr ep_){
- ep = ep_;
- callback_called = true;
- mtx.lock();
- mtx.unlock();
- cv.notify_one();
- };
- };
- template<typename... Args >
- void start_async(Args&&... args){
- this->crtp_cast_(this)->async(callback(), std::forward<Args>(args)...);
- }
- template<typename... Args >
- void start_async(cv::gapi::wip::GAsyncContext& ctx, Args&&... args){
- this->crtp_cast_(this)->async(ctx, callback(), std::forward<Args>(args)...);
- }
- void wait_for_result()
- {
- std::unique_lock<std::mutex> lck{mtx};
- cv.wait(lck,[&]{return callback_called == true;});
- if (ep)
- {
- std::rethrow_exception(ep);
- }
- }
- };
- //Test Mixin, hiding details of future based notification
- template<typename crtp_final_t>
- struct Future: crtp_cast<crtp_final_t> {
- std::future<void> f;
- template<typename... Args >
- void start_async(Args&&... args){
- f = this->crtp_cast_(this)->async(std::forward<Args>(args)...);
- }
- void wait_for_result()
- {
- f.get();
- }
- };
- //Test Mixin, hiding details of using compiled GAPI object
- template<typename crtp_final_t>
- struct AsyncCompiled : crtp_cast<crtp_final_t>{
- template<typename... Args>
- auto async(Args&&... args) -> decltype(cv::gapi::wip::async(std::declval<cv::GCompiled&>(), std::forward<Args>(args)...)){
- auto gcmpld = this->crtp_cast_(this)->compile();
- return cv::gapi::wip::async(gcmpld, std::forward<Args>(args)...);
- }
- template<typename... Args>
- auto async(cv::gapi::wip::GAsyncContext& ctx, Args&&... args) ->
- decltype(cv::gapi::wip::async(std::declval<cv::GCompiled&>(), std::forward<Args>(args)..., std::declval<cv::gapi::wip::GAsyncContext&>()))
- {
- auto gcmpld = this->crtp_cast_(this)->compile();
- return cv::gapi::wip::async(gcmpld, std::forward<Args>(args)..., ctx);
- }
- };
- //Test Mixin, hiding details of calling apply (async_apply) on GAPI Computation object
- template<typename crtp_final_t>
- struct AsyncApply : crtp_cast<crtp_final_t> {
- template<typename... Args>
- auto async(Args&&... args) ->
- decltype(cv::gapi::wip::async_apply(std::declval<cv::GComputation&>(), std::forward<Args>(args)..., std::declval<cv::GCompileArgs>()))
- {
- return cv::gapi::wip::async_apply(
- this->crtp_cast_(this)->computation(), std::forward<Args>(args)..., this->crtp_cast_(this)->compile_args()
- );
- }
- template<typename... Args>
- auto async(cv::gapi::wip::GAsyncContext& ctx, Args&&... args) ->
- decltype(cv::gapi::wip::async_apply(std::declval<cv::GComputation&>(), std::forward<Args>(args)... , std::declval<cv::GCompileArgs>(), std::declval<cv::gapi::wip::GAsyncContext&>()))
- {
- return cv::gapi::wip::async_apply(
- this->crtp_cast_(this)->computation(), std::forward<Args>(args)..., this->crtp_cast_(this)->compile_args(), ctx
- );
- }
- };
- template<typename case_t>
- struct normal: ::testing::Test, case_t{};
- TYPED_TEST_CASE_P(normal);
- TYPED_TEST_P(normal, basic){
- //Normal scenario: start function asynchronously and wait for the result, and verify it
- this->start_async(this->in_args(), this->out_args());
- this->wait_for_result();
- this->verify();
- }
- REGISTER_TYPED_TEST_CASE_P(normal,
- basic
- );
- template<typename case_t>
- struct exception: ::testing::Test, case_t{};
- TYPED_TEST_CASE_P(exception);
- TYPED_TEST_P(exception, basic){
- //Exceptional scenario: start function asynchronously and make sure exception is passed to the user
- this->start_async(this->in_args(), this->out_args());
- EXPECT_THROW(this->wait_for_result(), gthrow_exception);
- }
- REGISTER_TYPED_TEST_CASE_P(exception,
- basic
- );
- template<typename case_t>
- struct stress : ::testing::Test{};
- TYPED_TEST_CASE_P(stress);
- TYPED_TEST_P(stress, test){
- //Some stress testing: use a number of threads to start a bunch of async requests
- const std::size_t request_per_thread = 10;
- const std::size_t number_of_threads = 4;
- auto thread_body = [&](){
- std::vector<TypeParam> requests(request_per_thread);
- for (auto&& r : requests){
- r.start_async(r.in_args(), r.out_args());
- }
- for (auto&& r : requests){
- r.wait_for_result();
- r.verify();
- }
- };
- std::vector<std::thread> pool {number_of_threads};
- for (auto&& t : pool){
- t = std::thread{thread_body};
- }
- for (auto&& t : pool){
- t.join();
- }
- }
- REGISTER_TYPED_TEST_CASE_P(stress, test);
- template<typename case_t>
- struct cancel : ::testing::Test{};
- TYPED_TEST_CASE_P(cancel);
- TYPED_TEST_P(cancel, basic)
- {
- #if defined(__GNUC__) && __GNUC__ >= 11
- // std::vector<TypeParam> requests can't handle type with ctor parameter (SelfCanceling)
- FAIL() << "Test code is not available due to compilation error with GCC 11";
- #else
- constexpr int num_tasks = 100;
- cancel_struct cancel_struct_ {num_tasks};
- std::vector<TypeParam> requests; requests.reserve(num_tasks);
- for (auto i = num_tasks; i>0; i--){
- requests.emplace_back(&cancel_struct_);
- }
- for (auto&& r : requests){
- //first request will cancel other on it's execution
- r.start_async(cancel_struct_.ctx, r.in_args(), r.out_args());
- }
- unsigned int canceled = 0 ;
- for (auto&& r : requests){
- try {
- r.wait_for_result();
- }catch (cv::gapi::wip::GAsyncCanceled&){
- ++canceled;
- }
- }
- ASSERT_GT(canceled, 0u);
- #endif
- }
- namespace {
- GRunArgs deep_copy_out_args(const GRunArgsP& args ){
- GRunArgs result; result.reserve(args.size());
- for (auto&& arg : args){
- //FIXME: replace this switch with use of visit() on variant, when it will be available
- switch (arg.index()){
- case GRunArgP::index_of<cv::UMat*>() : result.emplace_back(*util::get<cv::UMat*>(arg)); break;
- case GRunArgP::index_of<cv::Mat*>() : result.emplace_back(*util::get<cv::Mat*>(arg)); break;
- case GRunArgP::index_of<cv::Scalar*>() : result.emplace_back(*util::get<cv::Scalar*> (arg)); break;
- case GRunArgP::index_of<cv::detail::VectorRef>() : result.emplace_back(util::get<cv::detail::VectorRef> (arg)); break;
- default : ;
- }
- }
- return result;
- }
- GRunArgsP args_p_from_args(GRunArgs& args){
- GRunArgsP result; result.reserve(args.size());
- for (auto&& arg : args){
- switch (arg.index()){
- case GRunArg::index_of<cv::Mat>() : result.emplace_back(&util::get<cv::Mat>(arg)); break;
- case GRunArg::index_of<cv::UMat>() : result.emplace_back(&util::get<cv::UMat>(arg)); break;
- case GRunArg::index_of<cv::Scalar>() : result.emplace_back(&util::get<cv::Scalar> (arg)); break;
- case GRunArg::index_of<cv::detail::VectorRef>() : result.emplace_back(util::get<cv::detail::VectorRef> (arg)); break;
- default : ;
- }
- }
- return result;
- }
- }
- REGISTER_TYPED_TEST_CASE_P(cancel, basic);
- template<typename case_t>
- struct output_args_lifetime : ::testing::Test{
- static constexpr const int num_of_requests = 20;
- };
- TYPED_TEST_CASE_P(output_args_lifetime);
- //There are intentionally no actual checks (asserts and verify) in output_args_lifetime tests.
- //They are more of example use-cases than real tests. (ASAN/valgrind can still catch issues here)
- TYPED_TEST_P(output_args_lifetime, callback){
- std::atomic<int> active_requests = {0};
- for (int i=0; i<this->num_of_requests; i++)
- {
- TypeParam r;
- //As output arguments are __captured by reference__ calling code
- //__must__ ensure they live long enough to complete asynchronous activity.
- //(i.e. live at least until callback is called)
- auto out_args_ptr = std::make_shared<cv::GRunArgs>(deep_copy_out_args(r.out_args()));
- //Extend lifetime of out_args_ptr content by capturing it into a callback
- auto cb = [&active_requests, out_args_ptr](std::exception_ptr ){
- --active_requests;
- };
- ++active_requests;
- r.async(cb, r.in_args(), args_p_from_args(*out_args_ptr));
- }
- while(active_requests){
- std::this_thread::sleep_for(std::chrono::milliseconds{2});
- }
- }
- TYPED_TEST_P(output_args_lifetime, future){
- std::vector<std::future<void>> fs(this->num_of_requests);
- std::vector<std::shared_ptr<cv::GRunArgs>> out_ptrs(this->num_of_requests);
- for (int i=0; i<this->num_of_requests; i++)
- {
- TypeParam r;
- //As output arguments are __captured by reference__ calling code
- //__must__ ensure they live long enough to complete asynchronous activity.
- //(i.e. live at least until future.get()/wait() is returned)
- auto out_args_ptr = std::make_shared<cv::GRunArgs>(deep_copy_out_args(r.out_args()));
- //Extend lifetime of out_args_ptr content
- out_ptrs[i] = out_args_ptr;
- fs[i] = r.async(r.in_args(), args_p_from_args(*out_args_ptr));
- }
- for (auto const& ftr : fs ){
- ftr.wait();
- }
- }
- REGISTER_TYPED_TEST_CASE_P(output_args_lifetime, callback, future);
- //little helpers to match up all combinations of setups
- template<typename compute_fixture_t, template<typename> class... args_t>
- struct Case
- : compute_fixture_t,
- args_t<Case<compute_fixture_t, args_t...>> ...
- {
- template<typename... Args>
- Case(Args&&... args) : compute_fixture_t(std::forward<Args>(args)...) { }
- Case(Case const & ) = default;
- Case(Case && ) = default;
- Case() = default;
- };
- template<typename computation_t>
- using cases = ::testing::Types<
- Case<computation_t, CallBack, AsyncCompiled>,
- Case<computation_t, CallBack, AsyncApply>,
- Case<computation_t, Future, AsyncCompiled>,
- Case<computation_t, Future, AsyncApply>
- >;
- INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPINormalFlow_, normal, cases<SumOfSum2x2>);
- INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIExceptionHandling_, exception, cases<ExceptionOnExecution>);
- INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIStress, stress, cases<SumOfSum2x2>);
- INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPICancelation, cancel, cases<SelfCanceling>);
- template<typename computation_t>
- using explicit_wait_cases = ::testing::Types<
- Case<computation_t, AsyncCompiled>,
- Case<computation_t, AsyncApply>,
- Case<computation_t, AsyncCompiled>,
- Case<computation_t, AsyncApply>
- >;
- INSTANTIATE_TYPED_TEST_CASE_P(AsyncAPIOutArgsLifetTime, output_args_lifetime, explicit_wait_cases<SumOfSum2x2>);
- } // namespace opencv_test
|