#include "Monitor.hpp"
#include "../DoneCallback.hpp"
#include "../DBus.hpp"
+#include "Dlog.hpp"
#include <mutex>
#include <chrono>
#include <fstream>
#include <cmath>
#include <type_traits>
+#include <regex>
namespace WrapDetails
std::string rootName;
};
Monitor<ContextInfo> contextInfo;
+ struct TTSInfo {
+ Optional<std::regex> searchLine;
+ bool found = false;
+ };
+ Monitor<TTSInfo> ttsInfo;
std::unordered_map<std::string, EvaluationValue> variables;
std::unordered_map<std::pair<detail::Kind, std::string>, EvaluationValue> setters;
std::unordered_map<std::pair<detail::Kind, std::string>, EvaluationValue> getters;
return {};
}
};
+ variables["wait_for_tts"] = detail::UserFunctionType<std::string> {
+ [&](EvaluationContext & ec, 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 << "'";
+ }
+
+ 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 {};
+ }
+ };
variables["wait_for_application"] = detail::UserFunctionType<std::string> {
[&](EvaluationContext & ec, std::string name) -> EvaluationValue {
auto h = contextInfo.lock();
auto untilMoment = std::chrono::high_resolution_clock::now() + 3000ms;
- auto success = h.waitForCondition(untilMoment, [&]() {
+ 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 << "'";
+ "current root name is '" << h->rootName << "'";
return {};
}
};
return {};
}
+ Dlog dlog;
+ if (!dlog.start()) {
+ output << "launching dlogutil failed\n";
+ return {};
+ }
// std::ostringstream os;
// result->debug(os, 0);
// output << os.str() << "\n";
output << "executing test '" << sourceName << "'\n";
auto exec = std::make_unique<TestExecutor>(*outputPtr);
- return std::thread { [result = std::move(result), exec = std::move(exec), outputPtr = std::move(outputPtr)]()
- {
- EvaluationContext &ec = exec->ec;
- exec->outputStream() << "waiting for context change...\n";
- bool hasContext = false;
- auto until = std::chrono::high_resolution_clock::now() + std::chrono::seconds{ 2 };
+ return std::thread { [result = std::move(result), exec = std::move(exec), outputPtr = std::move(outputPtr), dlog = std::move(dlog) ]() mutable
{
- auto h = exec->contextInfo.lock();
- hasContext = h.waitForCondition(until, [&]() {
- // h->root is pointer to root of visible at-spi hierarchy
- // if it's null, then either context building machinery didn't yet finish,
- // there's nothing visible to show or something went wrong
- // here we wait for it to be non-null (context finished constructing)
- // if it timeouts - something went wrong, so we bail out with an error
- return bool(h->root);
- });
- }
- if (hasContext) {
- exec->outputStream() << "evaluation started\n";
- try {
- result->evaluate(ec);
- exec->outputStream() << "evaluation successed\n";
- } catch (EvaluationFailure &e) {
- if (e.hasLocation())
- exec->outputStream() << e.location().toString() << ": ";
- exec->outputStream() << e.message() << "\nevaluation failed\n";
- } catch (...) {
- exec->outputStream() << "unhandled exception\nevaluation failed\n";
+ EvaluationContext &ec = exec->ec;
+ exec->outputStream() << "waiting for context change...\n";
+ auto until = std::chrono::high_resolution_clock::now() + std::chrono::seconds{ 2 };
+ bool hasContext = false;
+ {
+ auto h = exec->contextInfo.lock();
+ hasContext = h.waitForCondition(until, [&]()
+ {
+ // h->root is pointer to root of visible at-spi hierarchy
+ // if it's null, then either context building machinery didn't yet finish,
+ // there's nothing visible to show or something went wrong
+ // here we wait for it to be non-null (context finished constructing)
+ // if it timeouts - something went wrong, so we bail out with an error
+ return bool(h->root);
+ });
}
- } else {
- exec->outputStream() << "timeouted\n";
- DEBUG("timeouted, when waiting for context change");
- }
- outputPtr->flush();
- executeOnMainThread([]() {
- ecore_main_loop_quit();
- });
- DEBUG("done batch");
- } };
+ if (hasContext)
+ {
+ auto ttsMainRegex = std::regex {
+ "^D/SCREEN-READER[^>]*?> READ COMMAND PARAMS, TEXT: (.*?), DISCARDABLE: 0",
+ std::regex_constants::optimize | std::regex_constants::ECMAScript
+ };
+ 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);
+ }
+ }
+ });
+ exec->outputStream() << "evaluation started\n";
+ try {
+ result->evaluate(ec);
+ exec->outputStream() << "evaluation successed\n";
+ } catch (EvaluationFailure &e) {
+ if (e.hasLocation())
+ exec->outputStream() << e.location().toString() << ": ";
+ exec->outputStream() << e.message() << "\nevaluation failed\n";
+ } catch (...) {
+ exec->outputStream() << "unhandled exception\nevaluation failed\n";
+ }
+ } else {
+ exec->outputStream() << "timeouted\n";
+ DEBUG("timeouted, when waiting for context change");
+ }
+ outputPtr->flush();
+ executeOnMainThread([]()
+ {
+ ecore_main_loop_quit();
+ });
+ DEBUG("done batch");
+ } };
}
--- /dev/null
+#include "Dlog.hpp"
+#include "../UniversalSwitchLog.hpp"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <vector>
+#include <string>
+#include <sstream>
+
+Dlog::Handler::~Handler()
+{
+ auto h = state->lock();
+ h->listeners.erase(it);
+}
+
+Dlog::~Dlog()
+{
+ terminate();
+}
+
+auto Dlog::registerCallback(Callback c) -> Handler
+{
+ auto h = state->lock();
+ h->listeners.push_back(std::move(c));
+ auto it = --h->listeners.end();
+ return Handler{ state, it };
+}
+
+void Dlog::terminate()
+{
+ if (fdForClosing) {
+ char c = 0;
+ (void)write(*fdForClosing, &c, 1);
+ ASSERT(thr.joinable());
+ DEBUG("waiting for thread...");
+ thr.join();
+ DEBUG("waiting for thread done");
+ fdForClosing = {};
+ }
+}
+
+bool Dlog::start()
+{
+ int closingFd[2] = { -1, -1 };
+
+ if (pipe(closingFd) == -1) {
+ ERROR("failed to create pipes (%s)", strerror(errno));
+ return false;
+ }
+
+ auto const dlogFilePath = "/tmp/qe9fh483fb4i3fb8we3_universal_switch_dlog.txt";
+ int dlogFile = open(dlogFilePath, O_RDONLY);
+ if (dlogFile < 0) {
+ ERROR("failed to open file '%s' (%s)", dlogFilePath, strerror(errno));
+ return false;
+ }
+
+ fdForClosing = std::make_unique<int>(closingFd[1]);
+ thr = std::thread{ [state = this->state, closingFd = closingFd[0], dlogFile] {
+ std::vector<char> tmp(1024 * 128);
+ std::string line;
+ size_t pos = 0;
+
+ while (true)
+ {
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(closingFd, &set);
+ FD_SET(dlogFile, &set);
+ auto result = select(std::max(closingFd, dlogFile) + 1, &set, NULL, NULL, NULL);
+ if (result < 0) {
+ ERROR("select failed");
+ break;
+ }
+ if (FD_ISSET(closingFd, &set)) {
+ DEBUG("terminating...");
+ break;
+ }
+ if (FD_ISSET(dlogFile, &set)) {
+ if (pos + 1024 > tmp.size())
+ tmp.resize(tmp.size() * 2);
+ ASSERT(pos + 1024 <= tmp.size());
+ auto count = read(dlogFile, tmp.data() + pos, 1024);
+ if (count < 0) {
+ 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;
+ }
+ {
+ auto h = state->lock();
+ Optional<size_t> lastEndOfLine = {};
+ auto finalPos = pos + static_cast<size_t>(count);
+ for (; pos < finalPos; ++pos) {
+ if (tmp[pos] == '\n') {
+ auto first = lastEndOfLine ? (*lastEndOfLine + 1) : 0;
+ line.replace(0, line.size(), tmp.data() + first, pos - first);
+ ASSERT(line.size() == pos - first);
+ for (auto &f : h->listeners)
+ f(line);
+ lastEndOfLine = pos;
+ }
+ }
+ if (lastEndOfLine) {
+ auto remainingSize = pos - (*lastEndOfLine + 1);
+ memmove(tmp.data(), tmp.data() + *lastEndOfLine + 1, remainingSize);
+ pos = remainingSize;
+ }
+ }
+ } else {
+ DEBUG("spurious wake...");
+ }
+ }
+ close(closingFd);
+ close(dlogFile);
+ } };
+ return true;
+}
\ No newline at end of file