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;
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([&]()
};
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;
+ }
}
}
});
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();
Q(Vector)
Q(Set)
Q(Dict)
+Q(Wait)
#undef Q
std::string EvaluationValue_::typeName() const
};
}
+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;
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
*
Q(Vector)
Q(Set)
Q(Dict)
+ Q(Wait)
#undef Q
std::string typeName() const;
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;
}
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>
{
return function(args);
}
-protected:
- Type function;
static void validateArgumentCount(size_t got, size_t expectedMin, size_t expectedMax)
{
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)
Q(Vector)
Q(Set)
Q(Dict)
+Q(Wait)
#undef Q
std::ostream &operator << (std::ostream &s, const EvaluationValuePtr &v)
Q(Vector)
Q(Set)
Q(Dict)
+ Q(Wait)
#undef Q
size_t getSingleIndex(const EvaluationValuePtr &index, size_t size) const;
--- /dev/null
+#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);
+}
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
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);
+ }
+}
+
+
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
* 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, {
"<", ">", "(", ")", ",", "=", "[", "]", "+", "-", ".", "*", "/", "{", "}",
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.
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;
{
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
*
*/
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:
/**
*/
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);
}
};