Adds Monitor class for easier thread synchronization 97/164897/53
authorRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Thu, 21 Dec 2017 12:09:51 +0000 (13:09 +0100)
committerRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Mon, 22 Jan 2018 19:02:56 +0000 (20:02 +0100)
Adds Monitor and ValueOrExceptionMonitor classes for easier
synchronization and value passing between threads.

Change-Id: Id8374cfc60857b234c1db15dd51a8f279b0fcf97

src/NavigationInterface.cpp
src/batch/BatchRunner.cpp
src/batch/BatchRunner.hpp
src/batch/EvaluationValue.hpp
src/batch/Monitor.hpp [new file with mode: 0644]

index 7558b22..6a1173f 100644 (file)
@@ -97,9 +97,9 @@ public:
        NavigationImpl()
        {
 #ifdef DEBUG_DEBUGS_WANTED
-               DBus::setDebugPrinter([](auto str, auto strLen) {
-                       DEBUG("%s", std::string(str, strLen).c_str());
-               });
+               // DBus::setDebugPrinter([](auto str, auto strLen) {
+               //      DEBUG("%s", std::string(str, strLen).c_str());
+               // });
 #endif
                watchHandle = Singleton<UniversalSwitch>::instance().getAtspi()->registerWatcherForAllObjects(
                [this](const auto & src, auto eventType) {
index 0a887e6..b226532 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "BatchRunner.hpp"
 #include "../UniversalSwitch.hpp"
 #include "../UniversalSwitchLog.hpp"
 #include "Lexer.hpp"
 #include "../ActivityFactory.hpp"
 #include "../Observer.hpp"
 #include "../UIElement.hpp"
+#include "../UIActivity.hpp"
 #include "../NavigationInterface.hpp"
+#include "Monitor.hpp"
+#include "../DoneCallback.hpp"
+#include "../DBus.hpp"
 
 #include <mutex>
-#include <future>
 #include <chrono>
 #include <thread>
 #include <fstream>
+#include <cmath>
+#include <type_traits>
 
-struct ExecuteOnMainThreadHelper {
-       std::function<void()> callback;
+
+namespace WrapDetails
+{
+       template <typename MONITORED_TYPE, typename ... ARGS>
+       static std::function<void(DBus::ValueOrError<ARGS...>)> wrapImpl(
+               std::function<void(DBus::ValueOrError<ARGS...>)> func,
+               Monitor<BatchValueOrError<MONITORED_TYPE>> monitor)
+       {
+               return [monitor = std::move(monitor), func = std::move(func)](DBus::ValueOrError<ARGS...> val) -> void {
+                       try
+                       {
+                               if (!val) {
+                                       auto h = monitor.lock();
+                                       h->setError(EvaluationFailure{} << val.getError().message);
+                               } else {
+                                       func(std::move(val));
+                               }
+                       } catch (EvaluationFailure &ev)
+                       {
+                               auto h = monitor.lock();
+                               h->setError(std::move(ev));
+                       } catch (std::exception &ev)
+                       {
+                               auto h = monitor.lock();
+                               h->setError(EvaluationFailure{} << "unhandled exception (" << ev.what() << ")");
+                       } catch (...)
+                       {
+                               auto h = monitor.lock();
+                               h->setError(EvaluationFailure{} << "unhandled unknown exception");
+                       }
+               };
+       }
+
+       // (template magic) proxy function to discover arguments of the callable,
+       // called in wrap(...) function below. Typical usage is:
+       //   wrap(monitor, [](A a) { ... })
+       // at implementation of function wrap lambda is turned into type F, for which
+       // we want to know return type and it's arguments (we already know, it's callable,
+       // as it's required). So (in function wrap) we take F's operator () - it will become
+       // pointer to member. Now we call WrapHelper<F's operator ()>::call.
+       // This class has two specializations, both expect pointer to member (our operator () ).
+       // Template deduction allows compiler to discover both return type (which we know must be void)
+       // and it's arguments (we know in this case, that it must be single DBus::ValueOrError<ARGS...>).
+       // But now the compiler can tells us, which ARGS... exactly are, which will allow wrapImpl to create
+       // functor with the required arguments and return type.
+
+       template <typename T> struct WrapHelper;
+       template <typename C, typename ... ARGS>
+       struct WrapHelper<void(C::*)(DBus::ValueOrError<ARGS...>) const> {
+               template <typename MONITORED_TYPE>
+               static std::function<void(DBus::ValueOrError<ARGS...>)> call(
+                       std::function<void(DBus::ValueOrError<ARGS...>)> func,
+                       Monitor<BatchValueOrError<MONITORED_TYPE>> monitor)
+               {
+                       return wrapImpl(std::move(func), std::move(monitor));
+               }
+       };
+       template <typename C, typename ... ARGS>
+       struct WrapHelper<void(C::*)(DBus::ValueOrError<ARGS...>)> {
+               template <typename MONITORED_TYPE>
+               static std::function<void(DBus::ValueOrError<ARGS...>)> call(
+                       std::function<void(DBus::ValueOrError<ARGS...>)> func,
+                       Monitor<BatchValueOrError<MONITORED_TYPE>> monitor)
+               {
+                       return wrapImpl(std::move(func), std::move(monitor));
+               }
+       };
+}
+
+template <typename MONITORED_TYPE, typename F>
+auto wrap(Monitor<BatchValueOrError<MONITORED_TYPE>> monitor, F &&func)
+{
+       return WrapDetails::WrapHelper<decltype(&std::remove_reference<F>::type::operator())>::
+                  call(std::forward<F>(func), std::move(monitor));
+}
+
+class ExecutorOnMainThread
+{
+public:
+       template < typename F, typename = typename
+                          std::enable_if < !std::is_same<typename std::result_of<F()>::type, void>::value >::type >
+       static auto execute(F && function)
+       {
+               typename std::remove_reference<decltype(function())>::type ret;
+               ExecutorOnMainThread tmp{ [&]()
+               {
+                       ret = function();
+               } };
+               ecore_main_loop_thread_safe_call_sync(executeOnMainThreadCb, &tmp);
+               ASSERT(!eina_main_loop_is());
+               if (tmp.failure) throw *tmp.failure;
+               return ret;
+       }
+       template <typename F, typename = typename
+                         std::enable_if<std::is_same<typename std::result_of<F()>::type, void>::value>::type>
+       static void execute(F && function)
+       {
+               ExecutorOnMainThread tmp{ [&]()
+               {
+                       function();
+               } };
+               ecore_main_loop_thread_safe_call_sync(executeOnMainThreadCb, &tmp);
+               ASSERT(!eina_main_loop_is());
+               if (tmp.failure) throw *tmp.failure;
+               return;
+       }
+private:
+       ExecutorOnMainThread() = delete;
+       ExecutorOnMainThread(std::function<void()> function) : func(std::move(function)) { }
+       ExecutorOnMainThread(const ExecutorOnMainThread &) = delete;
+       ExecutorOnMainThread(ExecutorOnMainThread &&) = delete;
+
+       ExecutorOnMainThread &operator = (const ExecutorOnMainThread &) = delete;
+       ExecutorOnMainThread &operator = (ExecutorOnMainThread &&) = delete;
+
+       std::function<void()> func;
        Optional<EvaluationFailure> failure;
+
+       static void *executeOnMainThreadCb(void *d)
+       {
+               auto helper = static_cast<ExecutorOnMainThread *>(d);
+               try {
+                       helper->func();
+               } catch (EvaluationFailure &e) {
+                       helper->failure = std::move(e);
+               } catch (std::exception &e) {
+                       helper->failure = EvaluationFailure{} << "unhandled expection (" << e.what() << ")";
+               } catch (...) {
+                       helper->failure = EvaluationFailure{} << "unhandled expection";
+               }
+               return nullptr;
+       }
 };
-static void *executeOnMainThreadCb(void *d)
+
+template <typename T> auto executeOnMainThread(T &&fnc)
 {
-       auto helper = static_cast<ExecuteOnMainThreadHelper *>(d);
-       try {
-               helper->callback();
-       } catch (EvaluationFailure &e) {
-               helper->failure = std::move(e);
-       } catch (std::exception &e) {
-               helper->failure = EvaluationFailure{} << "unhandled expection (" << e.what() << ")";
-       } catch (...) {
-               helper->failure = EvaluationFailure{} << "unhandled expection";
-       }
-       return nullptr;
+       return ExecutorOnMainThread::execute(std::forward<T>(fnc));
 }
 
-void executeOnMainThread(std::function<void()> fnc)
+template <typename T, typename MONITORED_TYPE> MONITORED_TYPE executeOnMainThread(T &&fnc,
+               Monitor<BatchValueOrError<MONITORED_TYPE>> monitor,
+               std::chrono::microseconds timeout = 3s)
 {
-       ExecuteOnMainThreadHelper tmp{ std::move(fnc) };
-       ecore_main_loop_thread_safe_call_sync(executeOnMainThreadCb, &tmp);
-       if (tmp.failure) throw *tmp.failure;
+       auto untilMoment = std::chrono::high_resolution_clock::now() + timeout;
+       ExecutorOnMainThread::execute(std::forward<T>(fnc));
+       auto h = monitor.lock();
+       bool success = h.waitForCondition(untilMoment, [&]() {
+               return bool(*h);
+       });
+       if (!success)
+               throw EvaluationFailure{} << "evaluation timeouted";
+       ASSERT(*h);
+       return h->takeValue();
 }
 
 namespace std
@@ -104,85 +239,94 @@ struct TestExecutor : ExecutorInterface {
                                auto root = getVisibleRoot();
                                if (!root) throw EvaluationFailure{} << "no visible root (context changed didn't happen)";
                                ASSERT(root->getObject());
-                               std::vector<std::shared_ptr<UIElement>> result;
-                               executeOnMainThread([&]()
+                               Monitor<BatchValueOrError<std::vector<std::shared_ptr<UIElement>>>> monitor;
+
+                               return executeOnMainThread([&]()
                                {
-                                       getAllObjects(root->getObject(), [&](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> allElements) {
-                                               if (!allElements) throw EvaluationFailure{} << allElements.getError().message;
-                                               findByName(std::get<0>(allElements), name, [&](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements) {
-                                                       if (!elements) throw EvaluationFailure{} << elements.getError().message;
+                                       getAllObjects(root->getObject(), wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> allElements) {
+                                               findByName(std::get<0>(allElements), name, wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements) {
                                                        auto &elems = std::get<0>(elements);
-                                                       makeUIElements(std::move(elems), [&result](DBus::ValueOrError<std::vector<std::shared_ptr<UIElement>>> uiElems) {
-                                                               if (!uiElems) throw EvaluationFailure{} << uiElems.getError().message;
-                                                               result = std::move(std::get<0>(uiElems));
-                                                       });
-                                               });
-                                       }, std::move(roles), std::move(states));
-                               });
-                               return result;
+                                                       makeUIElements(std::move(elems), wrap(monitor, [ = ](DBus::ValueOrError<std::vector<std::shared_ptr<UIElement>>> uiElems) {
+                                                               auto h = monitor.lock();
+                                                               h->setValue(std::move(std::get<0>(uiElems)));
+                                                       }));
+                                               }));
+                                       }), std::move(roles), std::move(states));
+                               }, monitor);
                        }
                };
                getters[ { EvaluationValue::Kind::UIELEMENT, "name" }] = [&](const EvaluationValue & self) -> EvaluationValue {
                        auto uiElem = self.asUiElement();
                        ASSERT(uiElem->getObject());
-                       Optional<std::string> result;
-                       executeOnMainThread([&]()
+                       return executeOnMainThread([&]()
                        {
                                auto root = getVisibleRoot();
                                auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
                                auto found = atspi->getName(uiElem->getObject());
                                if (found)
-                                       result = std::move(*found);
+                                       return std::move(*found);
+                               throw EvaluationFailure{} << "failed to get at-spi object's name (use dlogutil to get at-spi error message)";
                        });
-                       if (!result) throw EvaluationFailure{} << "failed to get at-spi object's name (use dlogutil to get at-spi error message)";
-                       return *result;
                };
                getters[ { EvaluationValue::Kind::UIELEMENT, "middle_point" }] = [&](const EvaluationValue & self) -> EvaluationValue {
                        auto uiElem = self.asUiElement();
                        ASSERT(uiElem->getObject());
-                       Optional<Rectangle> result;
-                       executeOnMainThread([&]()
+                       return executeOnMainThread([&]()
                        {
                                auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
                                auto comp = atspi->getComponentInterface(uiElem->getObject());
                                if (comp) {
                                        auto found = atspi->getScreenPosition(comp);
                                        if (found)
-                                               result = *found;
+                                               return std::move(*found).getCenterPoint();
                                }
+                               throw EvaluationFailure{} << "failed to get at-spi object's screen position (use dlogutil to get at-spi error message)";
                        });
-                       if (!result) throw EvaluationFailure{} << "failed to get at-spi object's screen position (use dlogutil to get at-spi error message)";
-                       return (*result).getCenterPoint();
                };
                for (auto activityName : ActivityFactory::getInstance()->getAllActivityTypes()) {
                        variables[activityName] = EvaluationValue::FunctionType::Type{
                                [ = ](EvaluationContext & ec, std::vector<EvaluationValue> args) -> EvaluationValue {
-                                       executeOnMainThread([&]()
+                                       auto activity = executeOnMainThread([&]()
                                        {
-                                               DEBUG("calling activity %s", activityName.c_str());
-                                               auto activity = ActivityFactory::getInstance()->createActivity(activityName);
-                                               if (!activity)
-                                                       throw EvaluationFailure{} << "failed to construct '" << activityName << "' activity";
-                                               auto numOfArgs = activity->getRequiredNumberOfArgumentsIfAllowedInBatchProcessing();
-                                               if (!numOfArgs)
-                                                       throw EvaluationFailure{} << "activity '" << activityName << "' is not supported";
-                                               if (*numOfArgs != args.size())
-                                                       throw EvaluationFailure{} << "invalid number of arguments for activity '" << activityName <<
-                                                                                                         "', got " << args.size() << ", expected " << *numOfArgs;
-                                               auto activityObserver = std::dynamic_pointer_cast<Observer<UIElement>>(activity);
-                                               for (size_t i = 0; i < args.size(); ++i) {
-                                                       ASSERT(activityObserver);
-                                                       auto uiElement = detail::ConvertTo<std::shared_ptr<UIElement>>::convert(ec, std::move(args[i]));
-                                                       if (!uiElement)
-                                                               throw EvaluationFailure{} << "can't convert argument " << (i + 1) <<
-                                                                                                                 " of kind " << EvaluationValue::toString(args[i].kind()) << " to UIElement";
-                                                       activityObserver->update(std::move(uiElement));
-                                               }
-                                               activity->process({});
-                                               if (!activity->isCompleted())
-                                                       throw EvaluationFailure{} << "not enough arguments for activity '" << activityName << "'";
-                                               DEBUG("calling activity %s done", activityName.c_str());
+                                               return ActivityFactory::getInstance()->createActivity(activityName);
                                        });
+                                       if (!activity)
+                                               throw EvaluationFailure{} << "failed to construct '" << activityName << "' activity";
+                                       auto numOfArgs = activity->getRequiredNumberOfArgumentsIfAllowedInBatchProcessing();
+                                       if (!numOfArgs)
+                                               throw EvaluationFailure{} << "activity '" << activityName << "' is not supported";
+                                       if (*numOfArgs != args.size())
+                                               throw EvaluationFailure{} << "invalid number of arguments for activity '" << activityName <<
+                                                                                         "', got " << args.size() << ", expected " << *numOfArgs;
+                                       auto uiActivity = std::dynamic_pointer_cast<UIActivity>(activity);
+                                       if (!args.empty() && !uiActivity)
+                                               throw EvaluationFailure{} << "activity '" << activityName << "' doesn't inherit from Observer<UIElement>, "
+                                                                                         "which it must, becuase it expects arguments";
+                                       std::vector<std::shared_ptr<UIElement>> uiArgs(args.size());
+                                       for (size_t i = 0; i < args.size(); ++i)
+                                       {
+                                               ASSERT(uiActivity);
+                                               uiArgs[i] = detail::ConvertTo<std::shared_ptr<UIElement>>::convert(ec, std::move(args[i]));
+                                               if (!uiArgs[i])
+                                                       throw EvaluationFailure{} << "can't convert argument " << (i + 1) <<
+                                                                                                 " of kind " << EvaluationValue::toString(args[i].kind()) << " to UIElement";
+                                       }
+                                       Monitor<BatchValueOrError<bool>> monitor;
+                                       {
+                                               executeOnMainThread([&]()
+                                               {
+                                                       DEBUG("calling activity %s", activityName.c_str());
+                                                       for (auto &arg : uiArgs) {
+                                                               ASSERT(uiActivity);
+                                                               uiActivity->update(std::move(arg));
+                                                       }
+                                                       activity->process(DoneCallback{ [ = ] {
+                                                                       auto h = monitor.lock();
+                                                                       h->setValue(true);
+                                                               } });
+                                                       DEBUG("calling activity %s done", activityName.c_str());
+                                               }, monitor);
+                                       }
                                        return {};
                                } };
                }
@@ -237,14 +381,12 @@ struct TestExecutor : ExecutorInterface {
        }
        std::shared_ptr<UIElement> convert(Point pt) override
        {
-               std::shared_ptr<UIElement> result;
-               executeOnMainThread([&]() {
+               return executeOnMainThread([&]() {
                        auto root = getVisibleRoot();
                        auto atspi = Singleton<UniversalSwitch>::instance().getAtspi();
                        auto found = atspi->getAtPoint(pt, Atspi::CoordType::Screen, root->getObject());
-                       result = std::make_shared<UIElement>(std::move(found), pt, root->getApplicationCategory());
+                       return std::make_shared<UIElement>(std::move(found), pt, root->getApplicationCategory());
                });
-               return std::move(result);
        }
        void findByName(const std::vector<AtspiAccessiblePtr> &elems, std::string requestedName, std::function<void(DBus::ValueOrError<std::vector<AtspiAccessiblePtr>>)> callback)
        {
@@ -283,10 +425,11 @@ struct TestExecutor : ExecutorInterface {
                if (!col) {
                        callback(DBus::Error{ "root '" + Atspi::getUniqueId(root) + "' doesn't have collection interface" });
                } else {
+                       auto m = Atspi::Matcher();
+                       if (!states.empty()) m.states(states.begin(), states.end(), ATSPI_Collection_MATCH_ALL);
+                       if (!roles.empty()) m.roles(roles.begin(), roles.end(), ATSPI_Collection_MATCH_ANY);
                        atspi->getMatchedElements(col, ATSPI_Collection_SORT_ORDER_CANONICAL, 0,
-                                                                         Atspi::Matcher().states(states.begin(), states.end(), ATSPI_Collection_MATCH_ALL)
-                                                                         .roles(roles.begin(), roles.end(), ATSPI_Collection_MATCH_ANY)
-                                                                         , false, std::move(callback));
+                                                                         m, false, std::move(callback));
                }
        }
        void makeUIElement(AtspiAccessiblePtr src, std::function<void(DBus::ValueOrError<std::shared_ptr<UIElement>>)> callback)
@@ -344,75 +487,36 @@ struct TestExecutor : ExecutorInterface {
                }
        }
 
-       struct PromiseObject {
-               std::promise<std::shared_ptr<UIElement>> promise;
-               bool set = false;
-
-               void setValue(std::shared_ptr<UIElement> v)
-               {
-                       ASSERT(!set);
-                       promise.set_value(std::move(v));
-                       set = true;
-               }
-               template <typename T> void setException(T &&e)
-               {
-                       ASSERT(!set);
-                       promise.set_exception(std::make_exception_ptr(std::move(e)));
-                       set = true;
-               }
-
-               ~PromiseObject()
-               {
-                       if (!set) promise.set_value(nullptr);
-               }
-       };
        std::shared_ptr<UIElement> convert(const std::string &requestedName) override
        {
-               auto promiseObject = std::make_shared<PromiseObject>();
-               auto future = promiseObject->promise.get_future();
+               Monitor<BatchValueOrError<std::shared_ptr<UIElement>>> monitor;
 
-               executeOnMainThread([promiseObject = std::move(promiseObject), this, &requestedName]() {
+               return executeOnMainThread([&]() {
                        auto uiRoot = getVisibleRoot();
-                       if (!uiRoot) {
-                               promiseObject->setException(EvaluationFailure{} << "no visible root (context changed didn't happen)");
-                               return;
-                       }
+                       if (!uiRoot) throw EvaluationFailure{} << "no visible root (context change didn't happen)";
                        auto root = uiRoot->getObject();
                        ASSERT(root);
-                       getAllObjects(root, [promiseObject, &requestedName, this](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) {
-                               if (!promiseObject->set) {
-                                       if (!elems) {
-                                               promiseObject->setException(EvaluationFailure{} << elems.getError().message);
-                                               return;
+                       getAllObjects(root, wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elems) mutable {
+                               findByName(std::get<0>(elems), requestedName,
+                                                  wrap(monitor, [ = ](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements)
+                               {
+                                       auto &e = std::get<0>(elements);
+                                       if (e.empty()) {
+                                               throw EvaluationFailure{} << "no at-spi object found with name '" << requestedName << "'";
                                        }
-                                       findByName(std::get<0>(elems), requestedName,
-                                       [&requestedName, promiseObject, this](DBus::ValueOrError<std::vector<AtspiAccessiblePtr>> elements) {
-                                               if (!elements) {
-                                                       promiseObject->setException(EvaluationFailure{} << elements.getError().message);
-                                               } else {
-                                                       auto &e = std::get<0>(elements);
-                                                       if (e.empty()) {
-                                                               promiseObject->setException(EvaluationFailure{} << "no at-spi object found with name '" << requestedName << "'");
-                                                       } else if (e.size() > 1) {
-                                                               std::ostringstream names;
-                                                               for (auto &elem : e) names << " " << Atspi::getUniqueId(elem);
-                                                               promiseObject->setException(EvaluationFailure{} <<
-                                                                                                                       "found more, than one at-spi object with name '" << requestedName << "' (see" << names.str() << ")");
-                                                       } else {
-                                                               makeUIElement(e[0], [promiseObject](DBus::ValueOrError<std::shared_ptr<UIElement>> elem) {
-                                                                       if (!elem)
-                                                                               promiseObject->setException(EvaluationFailure{} << elem.getError().message);
-                                                                       else
-                                                                               promiseObject->setValue(std::move(std::get<0>(elem)));
-                                                               });
-                                                       }
-                                               }
-                                       });
-                               }
-                       });
-               });
-               future.wait();
-               return std::move(future.get());
+                                       if (e.size() > 1) {
+                                               std::ostringstream names;
+                                               for (auto &elem : e) names << " " << Atspi::getUniqueId(elem);
+                                               throw EvaluationFailure{} << "found more, than one at-spi object with name '" <<
+                                                                                                 requestedName << "' (see" << names.str() << ")";
+                                       }
+                                       makeUIElement(e[0], wrap(monitor, [ = ](DBus::ValueOrError<std::shared_ptr<UIElement>> elem) {
+                                               auto h = monitor.lock();
+                                               h->setValue(std::move(std::get<0>(elem)));
+                                       }));
+                               }));
+                       }));
+               }, monitor);
        }
 
        void insertStateConstants()
@@ -607,7 +711,7 @@ struct TestExecutor : ExecutorInterface {
        }
 };
 
-bool runTestCaseImpl(std::string sourceName, std::string outputName)
+bool runBatch(const std::string &sourceName, const std::string &outputName)
 {
        std::unique_ptr<std::ostream> outputPtr;
        outputPtr = std::make_unique<std::ofstream>(outputName.empty() ? "/dev/null" : outputName, std::ios_base::out);
@@ -662,7 +766,7 @@ bool runTestCaseImpl(std::string sourceName, std::string outputName)
        std::thread { [result, exec = std::move(exec), outputPtr = std::move(outputPtr)]()
        {
                EvaluationContext ec { *exec };
-               exec->outputStream() << "waiting for context change...";
+               exec->outputStream() << "waiting for context change...\n";
                auto until = std::chrono::high_resolution_clock::now() + std::chrono::seconds{ 2 };
                while (true) {
                        if (std::chrono::high_resolution_clock::now() >= until) {
@@ -687,6 +791,7 @@ bool runTestCaseImpl(std::string sourceName, std::string outputName)
                        }
                        std::this_thread::sleep_for(std::chrono::milliseconds{ 10 });
                }
+               outputPtr->flush();
 
                executeOnMainThread([]() {
                        ecore_main_loop_quit();
@@ -694,12 +799,3 @@ bool runTestCaseImpl(std::string sourceName, std::string outputName)
        } } .detach();
        return true;
 }
-
-void runBatch(const std::string &sourceName, const std::string &outputName)
-{
-       auto result = runTestCaseImpl(sourceName, outputName);
-       if (!result) {
-               Singleton<UniversalSwitch>::instance().terminate();
-               std::exit(1);
-       }
-}
index 5a95659..ae8dd43 100644 (file)
 #define BATCH_RUNNER_HPP
 
 #include <string>
+#include "EvaluationValue.hpp"
+#include "../Optional.hpp"
 
-void runBatch(const std::string &sourceName, const std::string &outputName);
+template <typename T> class BatchValueOrError
+{
+       Optional<T> data;
+       Optional<EvaluationFailure> error;
+public:
+       void setError(EvaluationFailure ev)
+       {
+               if (!*this) error = std::move(ev);
+       }
+       void setValue(T value)
+       {
+               if (!*this) data = std::move(value);
+       }
+       explicit operator bool () const
+       {
+               return data || error;
+       }
+
+       T takeValue() const
+       {
+               ASSERT(*this);
+               if (error) throw std::move(*error);
+               return std::move(*data);
+       }
+};
+
+bool runBatch(const std::string &sourceName, const std::string &outputName);
 
 #endif
index a9240ca..4d6e2c6 100644 (file)
@@ -22,6 +22,7 @@
 #include "../Atspi.hpp"
 #include "../UIElement.hpp"
 #include "../Geometry.hpp"
+#include "Monitor.hpp"
 #include <string>
 #include <tuple>
 #include <sstream>
@@ -50,7 +51,7 @@ public:
                (*text) << std::forward<T>(t);
                return *this;
        }
-       const char *what() const noexcept
+       const char *what() const noexcept override
        {
                cachedText = text->str();
                return cachedText.c_str();
diff --git a/src/batch/Monitor.hpp b/src/batch/Monitor.hpp
new file mode 100644 (file)
index 0000000..7f10651
--- /dev/null
@@ -0,0 +1,207 @@
+#ifndef MONITOR_HPP
+#define MONITOR_HPP
+
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
+#include "../Optional.hpp"
+
+/**
+ * @brief Monitor class
+ *
+ * Allows passing a template parameter T. Monitor object will store one instance of type T
+ * that will be accessible only to thread, that has acquired monitor (via detail::LockedData object).
+ *
+ * Monitor object allows thread synchronization. It offers three functionalities:
+ * 1. user can lock the monitor and access it's stored value. Only single thread might lock the monitor
+ *   simultaneously, others will wait. Order of acquiring is system dependant (random).
+ * 2. user, that have locked the monitor successfully, might decide to relinquish the lock and wait
+ *   for notification. In that case lock is released and thread is put to sleep.
+ * 3. user might notify one (or all) waiting threads from point 2. Order of waking is implementation
+ *   defined, as is which thread will be selected to be notified, when notifying only one.
+ *   Notified thread will be awaken and every such thread will try to reacquire the lock.
+ * You can read about monitors here https://en.wikipedia.org/wiki/Monitor_(synchronization) .
+ * Note, that this implementation is shared - Monitor and it's LockedMonitor might exists independetly of each other.
+ * LockedMonitor object is fully functional even after Monitor itself was destroyed.
+ */
+template <typename T> class Monitor
+{
+private:
+       /// \cond
+       struct Data {
+               std::mutex mt;
+               std::condition_variable cv;
+               T data;
+
+               Data() = default;
+               Data(T data) : data(std::move(data)) { }
+       };
+       std::shared_ptr<Data> ptr;
+       /// \endcond
+public:
+       Monitor() : ptr(std::make_shared<Data>()) {}
+       Monitor(T init) : ptr(std::make_shared<Data>(std::move(init))) { }
+
+       /**
+        * @brief Class representing lock over the monitor
+        *
+        * LockedMonitor object will (if still locked) unlock and notify all threads on
+        * destruction. LockedMonitor object might outlive Monitor itself, all data is
+        * shared using shared_ptr pointers.
+        */
+       class LockedMonitor
+       {
+       public:
+               LockedMonitor() = delete;
+               LockedMonitor(const Monitor &m) : lk(std::unique_lock<std::mutex>(m.ptr->mt)), ptr(m.ptr) { }
+
+               /**
+                * @brief Destructor - unlocks monitor
+                */
+               virtual ~LockedMonitor()
+               {
+                       ASSERT(lk);
+                       ptr->cv.notify_all();
+               }
+
+               /**
+                * @brief Move constructor
+                *
+                * You can safely move LockedMonitor object's around - source will be left "empty" (unlocked),
+                * destination will retain source state prior to the call.
+                */
+               LockedMonitor(LockedMonitor &&) = default;
+               LockedMonitor(const LockedMonitor &) = delete;
+
+               /**
+                * @brief Move assignment
+                *
+                * You can safely move LockedMonitor object's around - source will be left "empty" (unlocked),
+                * destination will retain source state prior to the call.
+                */
+               LockedMonitor &operator = (LockedMonitor &&) = default;
+               LockedMonitor &operator = (const LockedMonitor &) = delete;
+
+               /**
+                * @brief Releases mutex and waits for condition
+                *
+                * Releases the mutex, then wait for:
+                * - condition (see std::condition_variable for more details).
+                * - the mutex
+                * Condition itself will call predicate functor. If it returns true, the wait is considered
+                * to be successful and waitForCondition will return (with monitor still locked).
+                * During functor call the monitor itself is locked and functor might access the monitor's data safely.
+                */
+               void waitForCondition(std::function<bool()> predicate)
+               {
+                       ASSERT(lk && ptr);
+                       ptr->cv.wait(lk, std::move(predicate));
+               }
+
+               /**
+                * @brief Releases mutex and waits for condition to be notified, reacquire mutex after, but only for a given time
+                *
+                * See waitForCondition(std::function<bool()>) for a longer description. This version returns true,
+                * if completed successfully in requested time. The monitor is locked after
+                * waitForCondition returns.
+                */
+               bool waitForCondition(std::chrono::microseconds time, std::function<bool()> predicate)
+               {
+                       ASSERT(lk && ptr);
+                       return ptr->cv.wait_for(lk, time, std::move(predicate));
+               }
+
+               /**
+                * @brief Releases mutex and waits for condition to be notified, reacquire mutex after, but only until given moment of time
+                *
+                * See waitForCondition(std::function<bool()>) for a longer description. This version returns true,
+                * if completed successfully in requested time. The monitor is locked after
+                * waitForCondition returns.
+                */
+               bool waitForCondition(std::chrono::high_resolution_clock::time_point timePoint, std::function<bool()> predicate)
+               {
+                       ASSERT(lk && ptr);
+                       return ptr->cv.wait_until(lk, timePoint, std::move(predicate));
+               }
+
+               /**
+                * @brief operator * for access to data
+                *
+                * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+                * that LockedMonitor is locked).
+                */
+               T &operator * ()
+               {
+                       ASSERT(lk);
+                       return ptr->data;
+               }
+               /**
+                * @brief operator * for access to data
+                *
+                * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+                * that LockedMonitor is locked).
+                */
+               const T &operator * () const
+               {
+                       ASSERT(lk);
+                       return ptr->data;
+               }
+               /**
+                * @brief operator -> for access to data
+                *
+                * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+                * that LockedMonitor is locked).
+                */
+               T *operator -> ()
+               {
+                       ASSERT(lk);
+                       return &ptr->data;
+               }
+               /**
+                * @brief operator -> for access to data
+                *
+                * You should only access the data from the same thread, that created LockedMonitor (this will also ensure,
+                * that LockedMonitor is locked).
+                */
+               const T *operator -> () const
+               {
+                       ASSERT(lk);
+                       return &ptr->data;
+               }
+       private:
+               /// \cond
+               std::unique_lock<std::mutex> lk;
+               std::shared_ptr<Data> ptr;
+               /// \endcond
+       };
+
+       /**
+        * @brief LockedMonitor the monitor
+        *
+        * Locks the monitor, returns object reperesenting lock for the monitor and allowing accessing monitor's data.
+        * Returned object allows also to do notify-release-wait synchronization.
+        */
+       LockedMonitor lock() const
+       {
+               return LockedMonitor{ *this };
+       }
+       /**
+        * @brief Notifies (wakes) all threads waiting for condition variable
+        */
+       void notifyAll()
+       {
+               ptr->cv.notify_all();
+       }
+
+       /**
+        * @brief Notifies (wakes) at most one thread waiting for condition variable
+        *
+        * Thread is picked randomly from threads waiting on condition variable.
+        */
+       void notifyOne()
+       {
+               ptr->cv.notify_one();
+       }
+};
+
+#endif
\ No newline at end of file