Adds support for -> operator, which allows to wait for various events 45/167445/43
authorRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Wed, 17 Jan 2018 16:11:06 +0000 (17:11 +0100)
committerRadoslaw Cybulski <r.cybulski@partner.samsung.com>
Thu, 25 Jan 2018 15:50:30 +0000 (16:50 +0100)
Consider following situation (in batch mode): first you TAP on icon on
home screen and launch an application, then you want to TAP on some
icon inside the application. Now you've to wait for the application to
initialize and appear foreground. Operator -> is to solve this issue:

TAP('Phone') -> gui()

means execute left side (TAP), then wait for conditions on right side
(gui - context must change, new app appear and so on). Currently only
two conditions are supported - gui (new context, new window and so on)
and tts (screen reader did read something).

Change-Id: I72d00335c360acf97979825b73ed0c98040a0dc4

src/batch/BatchRunner.cpp
src/batch/Dlog.cpp
src/batch/EvaluationValue.cpp
src/batch/EvaluationValue.hpp
src/batch/EvaluationValueBase.cpp
src/batch/EvaluationValueBase.hpp
src/batch/EvaluationValueWait.cpp [new file with mode: 0644]
src/batch/Evaluator.cpp
src/batch/Evaluator.hpp
src/batch/Lexer.cpp
src/batch/Parser.cpp

index 33fbffe..8da02ae 100644 (file)
@@ -205,7 +205,8 @@ struct TestExecutor : ExecutorInterface {
        Monitor<ContextInfo> contextInfo;
        struct TTSInfo {
                Optional<std::regex> searchLine;
-               bool found = false;
+               enum class Mode { ignore, search, found };
+               Mode mode;
        };
        Monitor<TTSInfo> ttsInfo;
        std::unordered_map<std::string, EvaluationValue_> variables;
@@ -237,64 +238,116 @@ struct TestExecutor : ExecutorInterface {
                        if (!condition) throw EvaluationFailure{} << "assertion failed";
                        return {};
                };
-               variables["wait_for_tts"] = EvaluationValueFunction{ [&](std::string pattern) -> EvaluationValue_ {
+               variables["tts"] = EvaluationValueFunction{ [&](std::string pattern) -> EvaluationValue_ {
                        std::regex regex;
-                       try
-                       {
-                               regex = std::regex {
-                                       pattern,
-                                       std::regex_constants::nosubs |
-                                       std::regex_constants::optimize |
-                                       std::regex_constants::ECMAScript
-                               };
-                       } catch (...)
-                       {
-                               throw EvaluationFailure{} << "invalid regex pattern '" << pattern << "'";
+                       if (!pattern.empty()) {
+                               try
+                               {
+                                       regex = std::regex {
+                                               pattern,
+                                               std::regex_constants::nosubs |
+                                               std::regex_constants::optimize |
+                                               std::regex_constants::ECMAScript
+                                       };
+                               } catch (...)
+                               {
+                                       throw EvaluationFailure{} << "invalid regex pattern '" << pattern << "'";
+                               }
                        }
                        {
                                auto h = ttsInfo.lock();
-                               h->searchLine = std::move(regex);
-                               h->found = false;
-                               auto untilMoment = std::chrono::high_resolution_clock::now() + 3s;
-                               auto success = h.waitForCondition(untilMoment, [&]()
-                               {
-                                       return h->found;
-                               });
-                               if (!success)
-                                       throw EvaluationFailure{} << "wait_for_dlog('" << pattern << "'): operation timeouted";
-                               return {};
+                               if (!pattern.empty())
+                                       h->searchLine = std::move(regex);
+                               h->mode = TTSInfo::Mode::search;
                        }
-               }, { { "pattern", EvaluationValue_{} } } };
-               variables["wait_for_application"] = [&](std::string name) -> EvaluationValue_ {
-                       auto h = contextInfo.lock();
-                       auto untilMoment = std::chrono::high_resolution_clock::now() + 3000ms;
-                       auto success = h.waitForCondition(untilMoment, [&]() {
-                               return h->rootName == name;
-                       });
-                       if (!success)
-                               throw EvaluationFailure{} << "wait_for_application('" << name << "'): operation timeouted, " <<
-                                                                                               "current root name is '" << h->rootName << "'";
-                       return {};
-               };
-               variables["find_by_name"] = [&](std::string name, std::vector<int> roles, std::vector<int> states) -> EvaluationValue_ {
-                       auto root = getVisibleRoot();
-                       if (!root) throw EvaluationFailure{} << "no visible root (context changed didn't happen)";
-                       ASSERT(root->getObject());
-                       Monitor<BatchValueOrError<std::vector<std::shared_ptr<UIElement>>>> monitor;
-
-                       return executeOnMainThread([&]()
-                       {
-                               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), wrap(monitor, [ = ](DBus::ValueOrError<std::vector<std::shared_ptr<UIElement>>> uiElems) {
-                                                       auto h = monitor.lock();
-                                                       h->setValue(std::move(std::get<0>(uiElems)));
+                       struct Impl : public EvaluationValueWaitInterface {
+                               TestExecutor *self;
+                               std::chrono::high_resolution_clock::time_point untilMoment;
+                               std::string pattern;
+                               bool success = false;
+
+                               void join() override {
+                                       if (success) return;
+                                       auto h = self->ttsInfo.lock();
+                                       success = h.waitForCondition(untilMoment, [&]() {
+                                               return h->mode == TTSInfo::Mode::found;
+                                       });
+                                       if (!success) {
+                                               h->mode = TTSInfo::Mode::ignore;
+                                               throw EvaluationFailure{} << "wait for dlog ('" << pattern << "'): operation timeouted";
+                                       }
+                                       h->mode = TTSInfo::Mode::ignore;
+                               }
+                               bool isSatisfied() override {
+                                       if (success) return true;
+                                       auto h = self->ttsInfo.lock();
+                                       return success = (h->mode == TTSInfo::Mode::found);
+                               }
+                       };
+                       auto impl = std::make_shared<Impl>();
+                       impl->self = this;
+                       impl->untilMoment = std::chrono::high_resolution_clock::now() + 3s;
+                       impl->pattern = std::move(pattern);
+                       return EvaluationValue_{ impl };
+               }, { { "pattern", EvaluationValue_{ "" } } } };
+               variables["gui"] = EvaluationValueFunction{ [&](std::string name) -> EvaluationValue_ {
+                       struct Impl : public EvaluationValueWaitInterface {
+                               TestExecutor *self;
+                               std::chrono::high_resolution_clock::time_point untilMoment;
+                               std::string name;
+                               std::string currentRootName;
+                               bool success = false, searchForAnyChange = false;
+
+                               void join() override {
+                                       if (success) return;
+                                       auto h = self->contextInfo.lock();
+                                       success = h.waitForCondition(untilMoment, [&]() {
+                                               return 
+                                                       (searchForAnyChange && currentRootName != h->rootName) ||
+                                                       (!searchForAnyChange && h->rootName == name);
+                                       });
+                                       if (!success) {
+                                               throw EvaluationFailure{} << "wait for gui ('" << name << "'): operation timeouted, " <<
+                                                                                                         "current root name is '" << h->rootName << "'";
+                                       }                                       
+                               }
+                               bool isSatisfied() override {
+                                       if (success) return true;
+                                       auto h = self->contextInfo.lock();
+                                       return success = (h->rootName == name);
+                               }
+                       };
+                       auto impl = std::make_shared<Impl>();
+                       impl->self = this;
+                       impl->untilMoment = std::chrono::high_resolution_clock::now() + 3s;
+                       impl->name = std::move(name);
+                       if (impl->name.empty()) {
+                               auto h = contextInfo.lock();
+                               impl->currentRootName = h->rootName;
+                               impl->searchForAnyChange = true;
+                       }
+                       return EvaluationValue_{ impl };
+               }, { { "name", EvaluationValue_{ "" } } } };
+               variables["find_by_name"] = EvaluationValueFunction{ 
+                       [&](std::string name, std::vector<int> roles, std::vector<int> states) -> EvaluationValue_ {
+                               auto root = getVisibleRoot();
+                               if (!root) throw EvaluationFailure{} << "no visible root (context changed didn't happen)";
+                               ASSERT(root->getObject());
+                               Monitor<BatchValueOrError<std::vector<std::shared_ptr<UIElement>>>> monitor;
+
+                               return executeOnMainThread([&]()
+                               {
+                                       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), 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);
-               };
+                                       }), std::move(roles), std::move(states));
+                               }, monitor);
+               }, { { "name" }, { "roles", EvaluationValueSet() }, { "states", EvaluationValueSet() } } };
                for (auto activityName : ActivityFactory::getInstance()->getAllActivityTypes()) {
                        variables[activityName] = [ = ](EvaluationValueFunction::Args args) -> EvaluationValue_ {
                                auto activity = executeOnMainThread([&]()
@@ -828,11 +881,17 @@ Optional<std::thread> runBatch(const std::array<Optional<std::string>, (size_t)u
                                };
                                auto ttsDlogHandler = dlog.registerCallback([&exec, ttsMainRegex = std::move(ttsMainRegex)](const std::string & txt) {
                                        auto h = exec->ttsInfo.lock();
-                                       if (h->searchLine && !h->found) {
-                                               std::smatch result;
-                                               if (std::regex_search(txt, result, ttsMainRegex)) {
-                                                       auto sub = result[1].str();
-                                                       h->found = std::regex_search(sub, *h->searchLine);
+                                       if (h->mode == TestExecutor::TTSInfo::Mode::search) {
+                                               if (!h->searchLine) {
+                                                       h->mode = TestExecutor::TTSInfo::Mode::found;
+                                               }
+                                               else {
+                                                       std::smatch result;
+                                                       if (std::regex_search(txt, result, ttsMainRegex)) {
+                                                               auto sub = result[1].str();
+                                                               if (std::regex_search(sub, *h->searchLine))
+                                                                       h->mode = TestExecutor::TTSInfo::Mode::found;
+                                                       }
                                                }
                                        }
                                });
index 7a26f37..2c459bb 100644 (file)
@@ -86,12 +86,8 @@ bool Dlog::start()
                                                ERROR("failed to read from pipe (%s)", strerror(errno));
                                                break;
                                        } else if (count == 0) {
-                                               // sanity sleep to prevent consumption of all resources, when something goes hirewire
-                                               // and we'll get count = 0 all the time
-                                               // NOTE: for some reason this is currently mandatory, as select returns dlogFile as always ready
-                                               DEBUG("!!");
-                                               std::this_thread::sleep_for(std::chrono::milliseconds{ 10 });
-                                               continue;
+                                               ERROR("other side has close the pipe (%s)", strerror(errno));
+                                               break;
                                        }
                                        {
                                                auto h = state->lock();
index d873085..e5f7330 100644 (file)
@@ -94,6 +94,7 @@ Q(Function)
 Q(Vector)
 Q(Set)
 Q(Dict)
+Q(Wait)
 #undef Q
 
 std::string EvaluationValue_::typeName() const
index 1224d25..d62e6ea 100644 (file)
@@ -70,9 +70,11 @@ namespace std
        };
 }
 
+class EvaluationValueWaitInterface;
 class EvaluationContext;
 class EvaluationValueBase;
 using EvaluationValuePtr = std::shared_ptr<EvaluationValueBase>;
+using EvaluationValueWait = std::shared_ptr<EvaluationValueWaitInterface>;
 using EvaluationValueEmpty = std::nullptr_t;
 using EvaluationValueString = std::string;
 using EvaluationValueInteger = int64_t;
@@ -85,6 +87,16 @@ using EvaluationValueVector = std::vector<EvaluationValue_>;
 using EvaluationValueSet = std::unordered_set<EvaluationValue_>;
 using EvaluationValueDict = std::unordered_map<EvaluationValue_, EvaluationValue_>;
 
+class EvaluationValueWaitInterface
+{
+protected:
+       EvaluationValueWaitInterface() = default;
+       virtual ~EvaluationValueWaitInterface() = default;
+public:
+       virtual void join() = 0;
+       virtual bool isSatisfied() = 0;
+};
+
 /**
  * @brief Wrapper class containing batch script value 
  * 
@@ -181,6 +193,7 @@ public:
        Q(Vector)
        Q(Set)
        Q(Dict)
+       Q(Wait)
 #undef Q
 
        std::string typeName() const;
@@ -206,7 +219,7 @@ public:
        EvaluationValue_ attr_get(const std::string &name) const;
        EvaluationValue_ attr_set(const std::string &name, const EvaluationValue_ &value);
 
-       auto getInternalValue() const
+       const auto &getInternalValue() const
        {
                return value;
        }
@@ -453,6 +466,7 @@ public:
        private:
                const std::vector<EvaluationValue_> &args;
        };
+       
        EvaluationValueFunction() = default;
        template <typename T, typename ARGS_TUPLE = typename detail::get_args_as_tuple_from_lambda<T>::type,
                          typename = typename std::enable_if<detail::are_args_allowed_evaluation_value_from_tuple<ARGS_TUPLE>::value>::type>
@@ -474,8 +488,6 @@ public:
        {
                return function(args);
        }
-protected:
-       Type function;
 
        static void validateArgumentCount(size_t got, size_t expectedMin, size_t expectedMax)
        {
@@ -486,6 +498,8 @@ protected:
                        throw EvaluationFailure{} << "too many arguments, expected " <<
                                                                          (expectedMin < expectedMax ? "at most " : "") << expectedMax << ", got " << got;
        }
+protected:
+       Type function;
 private:
        template <typename ARGS_TUPLE, typename T>
        static EvaluationValueFunction::Type constructConvertingArgsFunction(T &&f, std::vector<Arg> argDescriptions)
index 1e1f229..ee71123 100644 (file)
@@ -28,6 +28,7 @@ Q(Function)
 Q(Vector)
 Q(Set)
 Q(Dict)
+Q(Wait)
 #undef Q
 
 std::ostream &operator << (std::ostream &s, const EvaluationValuePtr &v)
index b8f45f9..cb7ac23 100644 (file)
@@ -95,6 +95,7 @@ public:
        Q(Vector)
        Q(Set)
        Q(Dict)
+       Q(Wait)
 #undef Q
 
        size_t getSingleIndex(const EvaluationValuePtr &index, size_t size) const;
diff --git a/src/batch/EvaluationValueWait.cpp b/src/batch/EvaluationValueWait.cpp
new file mode 100644 (file)
index 0000000..3ee80c3
--- /dev/null
@@ -0,0 +1,50 @@
+#include "EvaluationValueBase.hpp"
+
+class EvaluationValueWaitImpl : public EvaluationValueBase
+{
+public:
+       EvaluationValueWaitImpl(EvaluationValueWait v) : value(v) { }
+
+       std::string typeName() const override
+       {
+               return "Wait";
+       }
+       bool isWait() const override
+       {
+               return true;
+       }
+       EvaluationValuePtr convertToWait() override
+       {
+               return shared_from_this();
+       }
+       const EvaluationValueWait &asWait() override
+       {
+               return value;
+       }
+
+       void writeToStream(std::ostream &os) const override
+       {
+               os << "<wait>";
+       }
+       bool oper_equal(const EvaluationValuePtr &other) override
+       {
+               return other->isBoolean() && value == other->asWait();
+       }
+       EvaluationValuePtr oper_call(const std::vector<EvaluationValue_> &args) override
+       {
+               EvaluationValueFunction::validateArgumentCount(args.size(), 0, 0);
+               value->join();
+               return create();
+       }
+       bool oper_is_true() override
+       {
+               return value->isSatisfied();
+       }
+private:
+       EvaluationValueWait value;
+};
+
+EvaluationValuePtr EvaluationValueBase::create(EvaluationValueWait v)
+{
+       return std::make_shared<EvaluationValueWaitImpl>(v);
+}
index 5eb0e93..d8d73da 100644 (file)
@@ -471,7 +471,9 @@ ExpressionAsStatementEvaluator::ExpressionAsStatementEvaluator(TokenLocation tok
 
 void ExpressionAsStatementEvaluator::evaluateImpl() const
 {
-       expr->evaluate();
+       auto r = expr->evaluate();
+       if (r.isWait() && !r && r.getInternalValue().use_count() == 1)
+               throw EvaluationFailure{} << "unsatisfied wait object is going out of scope, didn't you forget to apply -> to it";
 }
 
 void ExpressionAsStatementEvaluator::printSelfInfo(std::ostringstream &os, unsigned int depth) const
@@ -496,3 +498,36 @@ void BlockEvaluator::printSelfInfo(std::ostringstream &os, unsigned int depth) c
        for (auto &e : evals)
                e->printSelfInfo(os, depth + 1);
 }
+
+
+
+WaitEvaluator::WaitEvaluator(TokenLocation tokenLocation, StatPtr exec, ExprPtrs waits) :
+       StatementEvaluator(tokenLocation), exec(std::move(exec)), waits(std::move(waits)) { }
+
+void WaitEvaluator::evaluateImpl() const
+{
+       std::vector<EvaluationValue_> waitsValues;
+       waitsValues.reserve(waits.size());
+       for (auto &w : waits) {
+               auto r = w->evaluate();
+               waitsValues.push_back(r.convertToWait());
+       }
+       if (debugInterface) debugInterface->write("evaluating main block of wait clause");
+       exec->evaluate();
+       if (debugInterface) debugInterface->write("joining waiters");
+       for (auto &w : waitsValues) {
+               w.asWait()->join();
+       }
+}
+
+void WaitEvaluator::printSelfInfo(std::ostringstream &os, unsigned int depth) const
+{
+       printLocationAndIndent(os, location(), depth) << "wait\n";
+       exec->printSelfInfo(os, depth + 1);
+       for (auto &w : waits) {
+               printLocationAndIndent(os, location(), depth) << "for\n";
+               w->printSelfInfo(os, depth + 1);
+       }
+}
+
+
index 31fdff9..a8973e1 100644 (file)
@@ -420,4 +420,23 @@ public:
        void printSelfInfo(std::ostringstream &os, unsigned int depth) const override;
 };
 
+/**
+ * @brief Type representing group of statements, with wait conditions applied to them
+ * 
+ * exec represents statement (or group of statemenets), to be executed and waited for.
+ * waits are wait conditions - whatever those wait for must happen at the moment
+ * WaitEvaluator starts evaluating (or later). This statement will block
+ * until all waits are satisfied or timeout exception will be raised.
+ */
+class WaitEvaluator : public StatementEvaluator
+{
+       StatPtr exec;
+       ExprPtrs waits;
+public:
+       WaitEvaluator(TokenLocation location_, StatPtr exec, ExprPtrs waits);
+
+       void evaluateImpl() const override;
+       void printSelfInfo(std::ostringstream &os, unsigned int depth) const override;
+};
+
 #endif
\ No newline at end of file
index 9ea5d12..675ab1a 100644 (file)
@@ -88,7 +88,7 @@ static std::unordered_set<std::string> keywords {
  * Note, that when adding new operator, you must put it in collection with correct size (first element of the pair).
  */
 static std::vector<std::pair<unsigned int, std::unordered_set<std::string>>> operatorsbyLength {
-       { 2, { "==", "!=", "<=", ">=" } },
+       { 2, { "==", "!=", "<=", ">=", "->" } },
        {
                1, {
                        "<", ">", "(", ")", ",", "=", "[", "]", "+", "-", ".", "*", "/", "{", "}",
index 619c0e0..1a9a7c3 100644 (file)
@@ -165,6 +165,17 @@ class Parser
                return true;
        }
        /**
+        * @brief Returns true, if token of text t can be successfully consumed
+        *
+        * Returns true, if current token's text matches t.
+        * Current position is not modified.
+        */
+       bool canConsume(const std::string &t)
+       {
+               return index < tokens.size() && tokens[index].text() == t;
+       }
+
+       /**
         * @brief Tries to consume token with text t
         *
         * If current token's text matches t, advances current token and returns true.
@@ -192,7 +203,7 @@ class Parser
                args.clear();
 
                while (index < tokens.size()) {
-                       if (tokens[index].text() == ")") break;
+                       if (canConsume(")")) break;
                        if (!args.empty() && !consume(",")) return false;
                        auto e = parseExpr();
                        if (!e) return false;
@@ -479,6 +490,32 @@ class Parser
        {
                return parseComparisionsExpr();
        }
+
+       /**
+        * @brief Parse block of lines until end of file or closing }
+        *
+        * Note, that opening { if any must be already consumed and this function won't consume closing }
+        */
+       StatPtr parseBlockOfLines()
+       {
+               StatPtrs statements;
+               auto loc = tokens[index].location();
+               while (index < tokens.size()) {
+                       if (tokens[index].type() == TokenType::END_OF_LINE) {
+                               ++index;
+                               continue;
+                       }
+                       if (canConsume("}")) break;
+                       auto e = parseLine();
+                       if (e) {
+                               statements.push_back(std::move(e));
+                       } else {
+                               while (index < tokens.size() && tokens[index].type() != TokenType::END_OF_LINE) ++index;
+                               ++index;
+                       }
+               }
+               return std::make_shared<BlockEvaluator>(std::move(loc), std::move(statements));
+       }
        /**
         * @brief Tries to parse single line of code, which is suppoused to be expression
         *
@@ -486,10 +523,35 @@ class Parser
         */
        StatPtr parseLine()
        {
-               auto expr = parseExpr();
-               if (!expr) return {};
-               if (!consume(TokenType::END_OF_LINE)) return {};
-               return std::make_shared<ExpressionAsStatementEvaluator>(expr->location(), std::move(expr));
+               StatPtr line;
+               bool tryWait = true;
+
+               if (tokens[index].text() == "{") {
+                       ++index;
+                       line = parseBlockOfLines();
+                       if (!consume("}")) return {};
+               } else {
+                       auto expr = parseExpr();
+                       if (!expr) return {};
+                       if (!canConsume("->")) {
+                               if (!consume(TokenType::END_OF_LINE)) return {};
+                               tryWait = false;
+                       }
+                       line = std::make_shared<ExpressionAsStatementEvaluator>(expr->location(), std::move(expr));
+               }
+               if (tryWait && canConsume("->")) {
+                       ExprPtrs waits;
+                       auto loc = tokens[index].location();
+                       while (canConsume("->")) {
+                               ++index;
+                               auto r = parseExpr();
+                               if (!r) return {};
+                               waits.push_back(std::move(r));
+                       }
+                       if (!consume(TokenType::END_OF_LINE)) return {};
+                       line = std::make_shared<WaitEvaluator>(loc, std::move(line), std::move(waits));
+               }
+               return std::move(line);
        }
 public:
        /**
@@ -516,26 +578,18 @@ public:
         */
        StatPtr parse()
        {
-               if (index >= tokens.size()) {
+               index = 0;
+               if (tokens.empty()) {
                        error("empty file");
                        return {};
                }
-               StatPtrs statements;
-               auto loc = tokens[index].location();
-               while (index < tokens.size()) {
-                       if (tokens[index].type() == TokenType::END_OF_LINE) {
-                               ++index;
-                               continue;
-                       }
-                       auto e = parseLine();
-                       if (e) {
-                               statements.push_back(std::move(e));
-                       } else {
-                               while (index < tokens.size() && tokens[index].type() != TokenType::END_OF_LINE) ++index;
-                               ++index;
-                       }
+               auto res = parseBlockOfLines();
+               if (res && index < tokens.size()) {
+                       error("unexpected token '" + tokens[index].text() + "'");
+                       DEBUG(".");
+                       return {};
                }
-               return std::make_shared<BlockEvaluator>(std::move(loc), std::move(statements));
+               return std::move(res);
        }
 };