Add import(filename) method to BatchRunner 61/168761/15
authorPaweł Stawicki <p.stawicki@samsung.com>
Thu, 1 Feb 2018 12:51:22 +0000 (13:51 +0100)
committerPaweł Stawicki <p.stawicki@samsung.com>
Wed, 7 Feb 2018 14:28:26 +0000 (15:28 +0100)
Change-Id: Ied3a106417691d3f695c671873599defa6db2217

src/batch/BatchRunner.cpp
src/batch/BatchRunner.hpp
src/batch/EvaluationContext.cpp
src/batch/EvaluationContext.hpp
src/batch/Evaluator.cpp
src/batch/batch_exec.sh
src/batch/run-batch.sh
tests/no-ui-scenarios/BatchExecTests.cpp

index 33a64a4..668acb3 100644 (file)
@@ -203,6 +203,11 @@ BatchExecutor::BatchExecutor(std::ostream &output) : output(output)
 
 void BatchExecutor::registerContextChangeCallback()
 {
+       auto nav = Singleton<UniversalSwitch>::instance().getNavigationInterface();
+       if (!nav) {
+               DEBUG("no navigation interface found, context changed callback not registered");
+               return;
+       }
        auto updateContextInfo = [this](std::shared_ptr<UIElement> root, std::shared_ptr<NavigationElement> navigationContext) {
                std::string name;
                if (root && root->getObject()) {
@@ -216,10 +221,8 @@ void BatchExecutor::registerContextChangeCallback()
                h->root = std::move(root);
                h->rootName = std::move(name);
        };
-       auto nav = Singleton<UniversalSwitch>::instance().getNavigationInterface();
        updateContextInfo(nav->getCurrentVisibleRoot(), nav->getCurrentNavigationContext());
-       contextChangedHandle = Singleton<UniversalSwitch>::instance().getNavigationInterface()->
-                                                  registerCb<NavigationCallbackType::ContextChanged>(std::move(updateContextInfo));
+       contextChangedHandle = nav->registerCb<NavigationCallbackType::ContextChanged>(std::move(updateContextInfo));
 }
 
 EvaluationValue BatchExecutor::getVariableByName(const std::string &name)
@@ -405,8 +408,90 @@ std::shared_ptr<UIElement> BatchExecutor::convertToUIElement(const std::string &
        }, monitor);
 }
 
+std::string BatchExecutor::getFileContent(const std::string &filename)
+{
+       auto source = std::ifstream{ filename, std::ios_base::in };
+       if (!source.is_open())
+               throw EvaluationFailure{} << "failed to open file '" << filename << "'\n";
+
+       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)
+               throw EvaluationFailure{} <<  "error while reading file '" << filename << "'\n";
+
+       return std::string(bufor.data(), (size_t)total);
+}
+
+namespace
+{
+       //TODO: when switching to c++17 we should use std::filename::path
+       std::string getPathRelativeToFile(const std::string &relativePath, const std::string &file)
+       {
+               if (!relativePath.empty() && relativePath[0] == '/') {
+                       //not a relative path
+                       return relativePath;
+               }
+               auto pos = file.rfind('/');
+               if (pos == std::string::npos) {
+                       return relativePath;
+               }
+               return file.substr(0, pos + 1) + relativePath;
+       }
+}
+
 void BatchExecutor::insertMethods()
 {
+       variables["get_batch_file"] = [&]() -> EvaluationValue {
+               // this function is called through Evaluator.cpp:CallEvaluator::evaluate()
+               // which pushes it's own location to path stack, so path stack's top will be valid here.
+               return  EvaluationContext::getCurrentEvaluationContext().topBatchPath();
+       };
+
+       variables["import"] = [&](std::string filename) -> EvaluationValue {
+
+               StatPtr result;
+               auto currentRdlFile = EvaluationContext::getCurrentEvaluationContext().topBatchPath();
+               auto filenameFullPath = getPathRelativeToFile(filename, currentRdlFile);
+               auto it = imports.insert({ filenameFullPath, {} });
+               if (!it.second)
+               {
+                       result = it.first->second;
+                       if (!result) {
+                               throw EvaluationFailure{} << "nested import found in " << filenameFullPath;
+                       }
+                       result->evaluate();
+                       return {};
+               }
+
+               auto content = getFileContent(filenameFullPath);
+               std::string error;
+               auto tokens = lexTest(error, filenameFullPath, content);
+
+               if (!error.empty())
+                       throw EvaluationFailure{} << "Lexing failed\n" << error;
+
+               std::vector<std::string> errors;
+               result = parseTokens(errors, tokens);
+
+               if (!errors.empty())
+               {
+                       auto error = EvaluationFailure{};
+                       error << "Parsing failed\n";
+                       for (auto &e : errors) {
+                               error << e << "\n";
+                       }
+                       throw std::move(error);
+               }
+               ASSERT(result);
+               result->evaluate();
+               it.first->second = std::move(result);
+               return {};
+       };
+
        variables["sleep"] = [&](double tm) -> EvaluationValue {
                if (tm > 0)
                {
@@ -889,23 +974,10 @@ Optional<std::thread> runBatch(const std::array<Optional<std::string>, (size_t)u
                return {};
        }
        auto &output = *outputPtr;
-       auto source = std::ifstream{ sourceName, std::ios_base::in };
-       if (!source.is_open()) {
-               output << "failed to open file '" << sourceName << "'\n";
-               return {};
-       }
+       auto exec = std::make_unique<BatchExecutor>(*outputPtr);
 
-       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 {};
-       }
-       auto content = std::string{ bufor.data(), (size_t)total };
+       DEBUG("running batch file: %s", sourceName.c_str());
+       auto content = exec->getFileContent(sourceName);
 
        std::string error;
        auto tokens = lexTest(error, sourceName, content);
@@ -941,6 +1013,5 @@ Optional<std::thread> runBatch(const std::array<Optional<std::string>, (size_t)u
                *outputPtr << "Parsed source code:\n";
                *outputPtr << os.str() << "\n\n";
        }
-       auto exec = std::make_unique<BatchExecutor>(*outputPtr);
        return std::thread { threadFunc, std::move(result), std::move(exec), std::move(outputPtr), std::move(dlog) };
 }
index 66ba870..fdbde89 100644 (file)
@@ -78,6 +78,7 @@ public:
 
        EvaluationValue getVariableByName(const std::string &name) override;
        std::ostream &outputStream() override;
+       std::string getFileContent(const std::string &filename) override;
 
        std::shared_ptr<UIElement> getVisibleRoot() override;
        std::shared_ptr<UIElement> convertToUIElement(Point pt) override;
index 6878307..c2e7b16 100644 (file)
@@ -77,6 +77,23 @@ EvaluationValue EvaluationContext::getVariable(const Optional<TokenLocation> &lo
        }
 }
 
+void EvaluationContext::pushBatchPath(const std::string &path)
+{
+       batchPathStack.push_back(&path);
+}
+
+void EvaluationContext::popBatchPath()
+{
+       batchPathStack.pop_back();
+}
+
+const std::string &EvaluationContext::topBatchPath()
+{
+       ASSERT(!batchPathStack.empty());
+       return *batchPathStack.back();
+}
+
+
 std::string ExecutorInterface::getUIElementName(const std::shared_ptr<UIElement> &elem)
 {
        throw EvaluationFailure{} << "getUIElementName not implemeneted";
index 4f28e3e..9d5b2a6 100644 (file)
@@ -25,6 +25,7 @@
 #include <string>
 #include <ostream>
 #include <tuple>
+#include <vector>
 
 class UIElement;
 
@@ -96,6 +97,11 @@ struct ExecutorInterface {
         * There's only single stream for all kinds of output, debug or not.
         */
        virtual std::ostream &outputStream() = 0;
+
+       /**
+        * @brief Returns file content as a string
+        */
+       virtual std::string getFileContent(const std::string &filename) = 0;
 };
 
 /**
@@ -160,12 +166,32 @@ public:
         * getCurrentEvaluationContext function) only from the same thread, on which they were created.
         */
        static EvaluationContext &getCurrentEvaluationContext();
+
+       /**
+        * @brief Push script file name of the currently executing batch to stack
+        *
+        */
+       void pushBatchPath(const std::string &path);
+
+       /**
+        * @brief Remove last batch file name from stack
+        *
+        */
+       void popBatchPath();
+
+       /**
+        * @brief Get file name to currently executing batch file
+        *
+        */
+       const std::string &topBatchPath();
+
 private:
        void initializeGlobalContext(ExecutorInterface &ei);
 
        std::unordered_map<std::string, EvaluationValue> variables;
        EvaluationContext *parent = nullptr;
        std::shared_ptr<GlobalContext> globalContext;
+       std::vector<const std::string *> batchPathStack;
 };
 
 #endif
index ffad3d0..e1ffe96 100644 (file)
@@ -468,7 +468,10 @@ EvaluationValue CallEvaluator::evaluateImpl() const
        for (auto &it : keywordArgs) {
                keywordValues[it.first] = it.second->evaluate();
        }
-       return function->evaluate()(values, keywordValues);
+       EvaluationContext::getCurrentEvaluationContext().pushBatchPath(location().fileName());
+       auto result = function->evaluate()(values, keywordValues);
+       EvaluationContext::getCurrentEvaluationContext().popBatchPath();
+       return std::move(result);
 }
 
 void CallEvaluator::printSelfInfo(std::ostringstream &os, unsigned int depth) const
index 0eebcc0..c8cff9f 100755 (executable)
@@ -1,29 +1,62 @@
 #!/bin/bash
 
+BATCH_FILE=$(readlink -f "$1")
+BATCH_DIRNAME=$(dirname "$BATCH_FILE")
+
 PREFIX=qe9fh483fb4i3fb8we3_universal_switch
 DLOG=/tmp/${PREFIX}_dlog.txt
-TEST_FILE=/tmp/${PREFIX}_test_file
 OUTPUT=/tmp/${PREFIX}_output.txt
 
 PERMISSIONS=User::Pkg::org.tizen.universal-switch::RO
 
-dlogutil -c
-mkfifo -m 0666 $DLOG
-(
-       dlogutil '*' > $DLOG 2> /dev/null
-)&
-chsmack -a $PERMISSIONS $TEST_FILE
-chsmack -a $PERMISSIONS $DLOG
-JOBS=$(jobs -p)
-echo "JOB is $JOBS"
-
-rm -f $OUTPUT
-app_launcher -t org.tizen.universal-switch
-STATUS=$(app_launcher -s org.tizen.universal-switch TESTS_PATH $TEST_FILE TESTS_OUTPUT $OUTPUT)
-STATUS2=${STATUS#*successfully launched pid = }
-PID=${STATUS2% with debug*}
-tail --pid=$PID -f /dev/null
-kill -9 $JOBS
-
-rm -f $DLOG
-cat $OUTPUT
+
+function createDlogPipe()
+{
+    dlogutil -c
+    rm -f "$DLOG"
+
+    mkfifo -m 0666 "$DLOG"
+    (
+        dlogutil '*' > "$DLOG" 2> /dev/null
+    )&
+    chsmack -a $PERMISSIONS "$DLOG"
+
+    JOBS=$(jobs -p)
+    echo "JOB is $JOBS"
+}
+
+function doCleanup()
+{
+    app_launcher -t org.tizen.universal-switch
+    rm -f "$DLOG"
+    rm -f $OUTPUT
+    kill -9 $JOBS 2> /dev/null
+    exit
+}
+
+function runTest()
+{
+    rm -f $OUTPUT
+    app_launcher -t org.tizen.universal-switch
+    STATUS=$(app_launcher -s org.tizen.universal-switch TESTS_PATH $BATCH_FILE TESTS_OUTPUT $OUTPUT)
+    STATUS2=${STATUS#*successfully launched pid = }
+    PID=${STATUS2% with debug*}
+
+    tail --pid=$PID -f /dev/null
+    cat "$OUTPUT"
+}
+
+REPEAT=1
+(( "$2" > 1 )) 2>/dev/null && REPEAT="$2"
+
+trap doCleanup SIGINT
+
+createDlogPipe
+
+for((i=1;i<=$REPEAT;i++))
+do
+    echo "Test run: $i"
+    runTest
+done
+
+doCleanup
index 71a0a86..c03be41 100755 (executable)
@@ -1,14 +1,31 @@
 #!/bin/bash
 
-P=$(readlink -f "$1")
+BATCH_FILE=$(readlink -f "$1")
+BATCH_DIRNAME=$(dirname "$BATCH_FILE")
+BATCH_BASENAME=$(basename "$BATCH_FILE")
 SCRIPT=$(readlink -f "$0")
+SCRIPT_DIRNAME=$(dirname "$SCRIPT")
+BATCH_PATTERN='*.rdl'
+PERMISSIONS=User::Pkg::org.tizen.universal-switch::RO
+REPEAT="$2"
+
+TMP=$(mktemp -d)
+echo "tmp dir: $TMP"
+
+cd "$BATCH_DIRNAME"
+find . -name "$BATCH_PATTERN" -exec cp --parents {} "$TMP"  ";"
+cp "$BATCH_FILE" "$TMP"
+cp "$SCRIPT_DIRNAME/batch_exec.sh" "$TMP"
+sdb root on
+sdb push $TMP $TMP
+sdb shell chsmack -ra $PERMISSIONS "$TMP"
+
+
 cd $(dirname "$SCRIPT")
 cd ../..
-
 ./project-tool -j
-sdb root on
-sdb push $P /tmp/qe9fh483fb4i3fb8we3_universal_switch_test_file
-sdb push src/batch/batch_exec.sh /tmp/qe9fh483fb4i3fb8we3_universal_switch_test_exec
-sdb shell chsmack -a _ /tmp/qe9fh483fb4i3fb8we3_universal_switch_test_exec
-sdb shell "cd /tmp; /bin/bash /tmp/qe9fh483fb4i3fb8we3_universal_switch_test_exec; echo done"
 
+sdb shell "cd $TMP; /bin/bash batch_exec.sh $TMP/$BATCH_BASENAME $REPEAT ; echo done"
+
+rm -r "$TMP"
+sdb shell "rm -r $TMP"
\ No newline at end of file
index 606f148..6776851 100644 (file)
@@ -19,6 +19,7 @@
 #include "batch/Parser.hpp"
 #include "batch/EvaluationContext.hpp"
 #include "batch/Evaluator.hpp"
+#include "batch/BatchRunner.hpp"
 #include <sstream>
 
 using VarsType = std::unordered_map<std::string, EvaluationValue>;
@@ -46,6 +47,13 @@ struct TestExecutor : ExecutorInterface {
        {
                return std::cout;
        }
+       std::string getFileContent(const std::string &filename) override
+       {
+               ASSERT(0);
+               return {};
+       }
+
+
 private:
        VarsType variables;
        ActsType activities;
@@ -464,6 +472,93 @@ TEST_F(ScriptTest, keywordArguments)
        }
 }
 
+struct TestBatchExecutor : BatchExecutor {
+       TestBatchExecutor() : BatchExecutor(std::cout) { }
+
+       std::string getFileContent(const std::string &filename) override
+       {
+               auto it = files.find(filename);
+               if (it != files.end()) {
+                       return it->second;
+               }
+               return BatchExecutor::getFileContent(filename);
+       }
+       void addFileContent(std::string filename, std::string content)
+       {
+               files[std::move(filename)] = std::move(content);
+       }
+
+private:
+       std::unordered_map<std::string, std::string> files;
+};
+
+
+TEST_F(ScriptTest, Import)
+{
+       std::tuple<int, int, int, int> val, expected;
+       int funcCount = 0;
+
+       TestBatchExecutor exec;
+
+       exec.variables["func"] = EvaluationValueFunction {
+               [&](int a1, int a2, int a3, int a4) -> EvaluationValue {
+                       std::get<0>(val) = a1;
+                       std::get<1>(val) = a2;
+                       std::get<2>(val) = a3;
+                       std::get<3>(val) = a4;
+                       funcCount++;
+                       return {};
+               }, {
+                       { "a1" },
+                       { "a2" },
+                       { "a3", 1 },
+                       { "a4", 2 }
+               } };
+
+       EvaluationContext ec{ exec };
+
+       exec.addFileContent("test.rdl", "func(10,10,9,9)");
+       execute("import('test.rdl')", ec, true);
+       expected = { 10, 10, 9, 9};
+       ASSERT_EQ(val, expected);
+
+       //"test.rdl" file already parsed and cached, changes
+       // shouldn't be visible as long as test is running
+       exec.addFileContent("test.rdl", "func(12,12,11,11)");
+       execute("import('test.rdl')", ec, true);
+       expected = { 10, 10, 9, 9};
+       ASSERT_EQ(val, expected);
+
+       try {
+               execute("import('file_does_not_exist.rdl')", ec, true);
+               FAIL() << "file should not exist";
+       } catch (EvaluationFailure &e) {}
+
+       funcCount = 0;
+       exec.addFileContent("error.rdl", "func(1,2,3,4)\n"
+                                               "unknown_function(12,12,11,11)");
+       try {
+               execute("import('error.rdl')", ec, true);
+               FAIL() << "function should throw an evaluation error";
+       } catch (EvaluationFailure &e) {}
+       ASSERT_EQ(funcCount, 1);
+
+       funcCount = 0;
+       exec.addFileContent("recursive.rdl", "func(1,2,3,4)\n"
+                                               "import('recursive.rdl')");
+       try {
+               execute("import('recursive.rdl')", ec, true);
+               FAIL() << "nested import";
+       } catch (EvaluationFailure &e) {}
+       ASSERT_EQ(funcCount, 1);
+
+       funcCount = 0;
+       exec.addFileContent("test2.rdl", "func(1,2,3,4)");
+       execute("import('test2.rdl')\n"
+                       "import('test2.rdl')", ec, true);
+       ASSERT_EQ(funcCount, 2);
+}
+
 int main(int argc, char *argv[])
 {
        try {