--- /dev/null
+/*
+ * Copyright 2017 Samsung Electronics Co., Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../UniversalSwitch.hpp"
+#include "../UniversalSwitchLog.hpp"
+#include "Lexer.hpp"
+#include "Parser.hpp"
+#include "EvaluationContext.hpp"
+#include "Evaluator.hpp"
+#include "../UIElement.hpp"
+#include "../NavigationInterface.hpp"
+
+#include <mutex>
+#include <chrono>
+#include <thread>
+#include <fstream>
+
+struct ExecuteOnMainThreadHelper {
+ std::function<void()> callback;
+ Optional<EvaluationFailure> failure;
+};
+static void *executeOnMainThreadCb(void *d)
+{
+ 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;
+}
+
+void executeOnMainThread(std::function<void()> fnc)
+{
+ ExecuteOnMainThreadHelper tmp{ std::move(fnc) };
+ ecore_main_loop_thread_safe_call_sync(executeOnMainThreadCb, &tmp);
+ if (tmp.failure) throw *tmp.failure;
+}
+
+struct TestExecutor : ExecutorInterface {
+ NavigationInterface::CallbackHandle contextChangedHandle;
+ std::shared_ptr<UIElement> root;
+ std::shared_ptr<NavigationElement> navigationContext;
+ std::unordered_map<std::string, EvaluationValue> variables;
+ std::mutex mt;
+ std::ostream &output;
+
+ // NOTE: constructor of TestExecutor must be called on main thread
+ // constructor calls NavigationInterface object, which might be
+ // in progress of updating itself (context change) on main thread
+ TestExecutor(std::ostream &output) : output(output)
+ {
+ variables["sleep"] = EvaluationValue::UserFunctionType<double> {
+ [&](EvaluationContext & ec, double tm) -> EvaluationValue {
+ if (tm > 0)
+ {
+ auto sleepTime = std::chrono::milliseconds{ static_cast<int>(std::floor(1000.0 * tm + 0.5)) };
+ std::this_thread::sleep_for(sleepTime);
+ }
+ return {};
+ }
+ };
+ auto nav = Singleton<UniversalSwitch>::instance().getNavigationInterface();
+ this->navigationContext = nav->getCurrentNavigationContext();
+ this->root = nav->getCurrentVisibleRoot();
+ contextChangedHandle = Singleton<UniversalSwitch>::instance().getNavigationInterface()->registerCb<NavigationCallbackType::ContextChanged>(
+ [this](std::shared_ptr<UIElement> root, std::shared_ptr<NavigationElement> navigationContext) {
+ ASSERT(navigationContext);
+ std::lock_guard<std::mutex> lock(mt);
+ this->navigationContext = std::move(navigationContext);
+ this->root = std::move(root);
+ });
+ }
+ ~TestExecutor() = default;
+ EvaluationValue getVariableByName(const std::string &name) override
+ {
+ auto it = variables.find(name);
+ if (it != variables.end())
+ return it->second;
+ return ExecutorInterface::getVariableByName(name);
+ }
+ std::shared_ptr<UIElement> getVisibleRoot() override
+ {
+ std::lock_guard<std::mutex> lock(mt);
+ return root;
+ }
+
+ std::ostream &outputStream() override
+ {
+ return output;
+ }
+};
+
+bool runTestCaseImpl(std::string sourceName, std::string outputName)
+{
+ std::unique_ptr<std::ostream> outputPtr;
+ outputPtr = std::make_unique<std::ofstream>(outputName.empty() ? "/dev/null" : outputName, std::ios_base::out);
+
+ if (!static_cast<std::ofstream *>(outputPtr.get())->is_open()) {
+ ERROR("failed to open output file '%s'", outputName.c_str());
+ return false;
+ }
+ auto &output = *outputPtr;
+ auto source = std::ifstream{ sourceName, std::ios_base::in };
+ if (!source.is_open()) {
+ output << "failed to open file '" << sourceName << "'\n";
+ return false;
+ }
+
+ source.seekg(0, std::ios_base::end);
+ auto total = source.tellg();
+ source.seekg(0);
+ std::vector<char> bufor;
+ bufor.resize(total);
+ source.read(bufor.data(), total);
+ if ((size_t)source.gcount() != total) {
+ output << "error while reading file '" << sourceName << "'\n";
+ return false;
+ }
+ auto content = std::string{ bufor.data(), (size_t)total };
+
+ std::string error;
+ auto tokens = lexTest(error, sourceName, content);
+ if (!error.empty()) {
+ output << error << "\nlexing failed\n";
+ return false;
+ }
+
+ std::vector<std::string> errors;
+ auto result = parseTokens(errors, tokens);
+
+ if (!errors.empty() || !result) {
+ for (auto &e : errors) {
+ output << e << "\n";
+ }
+ output << "parsing failed\n";
+ return false;
+ }
+
+ // std::ostringstream os;
+ // result->debug(os, 0);
+ // output << os.str() << "\n";
+
+ output << "executing test '" << sourceName << "'\n";
+ auto exec = std::make_unique<TestExecutor>(*outputPtr);
+ std::thread { [result, exec = std::move(exec), outputPtr = std::move(outputPtr)]()
+ {
+ EvaluationContext ec { *exec };
+ exec->outputStream() << "waiting for context change...";
+ auto until = std::chrono::high_resolution_clock::now() + std::chrono::seconds{ 2 };
+ while (true) {
+ if (std::chrono::high_resolution_clock::now() >= until) {
+ exec->outputStream() << "timeouted\n";
+ DEBUG("timeouted, when waiting for context change");
+ break;
+ }
+ if (exec->getVisibleRoot()) {
+ exec->outputStream() << "evaluation started\n";
+ try {
+ result->evaluate(ec);
+ exec->outputStream() << "evaluation successed\n";
+ std::this_thread::sleep_for(std::chrono::seconds{ 1 });
+ } 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";
+ }
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds{ 10 });
+ }
+
+ executeOnMainThread([]() {
+ ecore_main_loop_quit();
+ });
+ } } .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);
+ }
+}