diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e1337d..e6c29b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ set_target_properties(effil PROPERTIES COMPILE_FLAGS "${ENABLE_WARNINGS} ${GENER #---------- FILE(GLOB TEST_SOURCES tests/cpp/*.cpp tests/cpp/*.h) -FILE(GLOB LUA_TEST_SOURCES tests/lua/*.lua) +FILE(GLOB LUA_TEST_SOURCES tests/lua/run_tests.lua) set(GTEST_DIR libs/gtest/googletest) include_directories(${GTEST_DIR}/include ${GTEST_DIR}) diff --git a/src/cpp/lua-module.cpp b/src/cpp/lua-module.cpp index e492698..7ae4648 100644 --- a/src/cpp/lua-module.cpp +++ b/src/cpp/lua-module.cpp @@ -5,11 +5,11 @@ namespace { -static sol::object createThread(sol::this_state lua, sol::function func, const sol::variadic_args &args) noexcept { - return sol::make_object(lua, std::make_unique(func, args)); +sol::object createThreadFactory(sol::this_state lua, const sol::function& func) { + return sol::make_object(lua, std::make_unique(func)); } -static sol::object createShare(sol::this_state lua) noexcept { +sol::object createShare(sol::this_state lua) { return sol::make_object(lua, std::make_unique()); } @@ -19,8 +19,12 @@ extern "C" int luaopen_libeffil(lua_State *L) { sol::state_view lua(L); effil::LuaThread::getUserType(lua); effil::SharedTable::getUserType(lua); + effil::ThreadFactory::getUserType(lua); sol::table public_api = lua.create_table_with( - "thread", createThread, + "thread", createThreadFactory, + "thread_id", effil::threadId, + "sleep", effil::sleep, + "yield", effil::yield, "share", createShare ); sol::stack::push(lua, public_api); diff --git a/src/cpp/shared-table.cpp b/src/cpp/shared-table.cpp index db3a6a3..bb154e2 100644 --- a/src/cpp/shared-table.cpp +++ b/src/cpp/shared-table.cpp @@ -7,7 +7,7 @@ namespace effil { sol::object SharedTable::getUserType(sol::state_view &lua) noexcept { static sol::usertype type( - sol::call_construction(), sol::default_constructor, + "new", sol::no_constructor, sol::meta_function::new_index, &SharedTable::luaSet, sol::meta_function::index, &SharedTable::luaGet, sol::meta_function::length, &SharedTable::size @@ -34,8 +34,8 @@ void SharedTable::luaSet(const sol::stack_object& luaKey, const sol::stack_objec } } -sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_state& state) const noexcept { - assert(key.valid()); +sol::object SharedTable::luaGet(const sol::stack_object& key, const sol::this_state& state) const { + ASSERT(key.valid()); StoredObject cppKey(key); std::lock_guard g(lock_); diff --git a/src/cpp/shared-table.h b/src/cpp/shared-table.h index 5ab3cf9..a04f769 100644 --- a/src/cpp/shared-table.h +++ b/src/cpp/shared-table.h @@ -22,7 +22,7 @@ public: public: // lua bindings void luaSet(const sol::stack_object& luaKey, const sol::stack_object& luaValue); - sol::object luaGet(const sol::stack_object& key, const sol::this_state& state) const noexcept; + sol::object luaGet(const sol::stack_object& key, const sol::this_state& state) const; protected: mutable SpinMutex lock_; diff --git a/src/cpp/stored-object.cpp b/src/cpp/stored-object.cpp index e6fcad1..54060c9 100644 --- a/src/cpp/stored-object.cpp +++ b/src/cpp/stored-object.cpp @@ -33,7 +33,7 @@ public: return std::hash()(data_); } - sol::object unpack(sol::this_state state) const noexcept final { + sol::object unpack(sol::this_state state) const final { return sol::make_object(state, data_); } @@ -59,7 +59,7 @@ public: return std::hash()(function_); } - sol::object unpack(sol::this_state state) const noexcept final { + sol::object unpack(sol::this_state state) const final { sol::state_view lua((lua_State*)state); sol::function loader = lua["loadstring"]; ASSERT(loader.valid()); @@ -77,9 +77,9 @@ private: // TODO: Trick is - sol::object has only operator==:/ typedef std::vector> SolTableToShared; -void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) noexcept; +void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited); -StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) noexcept { +StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) { if (luaObject.get_type() == sol::type::table) { sol::table luaTable = luaObject; auto comparator = [&luaTable](const std::pair& element){ @@ -100,7 +100,7 @@ StoredObject makeStoredObject(sol::object luaObject, SolTableToShared& visited) } } -void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) noexcept { +void dumpTable(SharedTable* target, sol::table luaTable, SolTableToShared& visited) { for(auto& row : luaTable) { target->set(makeStoredObject(row.first, visited), makeStoredObject(row.second, visited)); } @@ -150,11 +150,11 @@ StoredObject::StoredObject(SharedTable* table) noexcept : data_(new PrimitiveHolder(table)) { } -StoredObject::StoredObject(const sol::object& object) noexcept +StoredObject::StoredObject(const sol::object& object) : data_(fromSolObject(object)) { } -StoredObject::StoredObject(const sol::stack_object& object) noexcept +StoredObject::StoredObject(const sol::stack_object& object) : data_(fromSolObject(object)) { } @@ -169,7 +169,7 @@ std::size_t StoredObject::hash() const noexcept { return 0; } -sol::object StoredObject::unpack(sol::this_state state) const noexcept { +sol::object StoredObject::unpack(sol::this_state state) const { if (data_) return data_->unpack(state); else diff --git a/src/cpp/stored-object.h b/src/cpp/stored-object.h index 32ce2f6..276afdf 100644 --- a/src/cpp/stored-object.h +++ b/src/cpp/stored-object.h @@ -17,7 +17,7 @@ public: } virtual std::size_t hash() const noexcept = 0; - virtual sol::object unpack(sol::this_state state) const noexcept = 0; + virtual sol::object unpack(sol::this_state state) const = 0; private: BaseHolder(const BaseHolder&) = delete; @@ -31,12 +31,12 @@ public: StoredObject() = default; StoredObject(StoredObject&& init) noexcept; StoredObject(SharedTable*) noexcept; - StoredObject(const sol::object&) noexcept; - StoredObject(const sol::stack_object&) noexcept; + StoredObject(const sol::object&); + StoredObject(const sol::stack_object&); operator bool() const noexcept; std::size_t hash() const noexcept; - sol::object unpack(sol::this_state state) const noexcept; + sol::object unpack(sol::this_state state) const; StoredObject& operator=(StoredObject&& o) noexcept; bool operator==(const StoredObject& o) const noexcept; diff --git a/src/cpp/threading.cpp b/src/cpp/threading.cpp index d54d2a1..c4f92b1 100644 --- a/src/cpp/threading.cpp +++ b/src/cpp/threading.cpp @@ -1,76 +1,258 @@ #include "threading.h" +#include "stored-object.h" namespace effil { -LuaThread::LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept { - // 1. Dump function to string - sol::state_view lua(function.lua_state()); - str_function_ = lua["string"]["dump"](function); +class LuaHookStopException : public std::exception {}; - // 2. Create new state - p_state_.reset(new sol::state); - ASSERT(p_state_.get() != NULL); - p_state_->open_libraries( - sol::lib::base, sol::lib::string, - sol::lib::package, sol::lib::io, sol::lib::os - ); - getUserType(*p_state_); - effil::SharedTable::getUserType(*p_state_); - - // 3. Save parameters - storeArgs(args); - - // 4. Run thread - p_thread_.reset(new std::thread(&LuaThread::work, this)); - ASSERT(p_thread_.get() != NULL); -} - -void LuaThread::storeArgs(const sol::variadic_args &args) noexcept { - p_arguments_ = std::make_shared>(); - for(auto iter = args.begin(); iter != args.end(); iter++) { - effil::StoredObject store(iter->get()); - p_arguments_->push_back(store.unpack(sol::this_state{p_state_->lua_state()})); - } -} - -void LuaThread::join() noexcept { - if (p_thread_.get()) { - p_thread_->join(); - p_thread_.reset(); - } - if (p_arguments_.get()) - p_arguments_.reset(); - if (p_state_.get()) - p_state_.reset(); -} - -void LuaThread::detach() noexcept { - p_thread_->detach(); -} - -void LuaThread::work() noexcept { - ASSERT(p_state_.get() && p_arguments_.get()) << "invalid thread Lua state\n"; - - std::string func_owner = std::move(str_function_); - std::shared_ptr state_owner = p_state_; - std::shared_ptr> arguments_owner = p_arguments_; - sol::function_result func = (*state_owner)["loadstring"](func_owner); - func.get()(sol::as_args(*arguments_owner)); -} - -std::string LuaThread::threadId() noexcept { +std::string threadId() noexcept +{ std::stringstream ss; ss << std::this_thread::get_id(); return ss.str(); } +void yield() noexcept +{ + std::this_thread::yield(); +} + +void sleep(int64_t time, sol::optional period) noexcept +{ + std::string metric = period ? period.value() : "s"; + if (metric == "ms") + std::this_thread::sleep_for(std::chrono::milliseconds(time)); + else if (metric == "s") + std::this_thread::sleep_for(std::chrono::seconds(time)); + else if (metric == "m") + std::this_thread::sleep_for(std::chrono::minutes(time)); + else + throw sol::error("invalid time identificator: " + metric); +} + +thread_local LuaThread::ThreadData* LuaThread::pThreadLocalData = NULL; + +// class LuaThread + +LuaThread::LuaThread(std::shared_ptr threadData, const std::string& function, const sol::variadic_args& args) { + pThreadData_ = threadData; + ASSERT(pThreadData_.get()); + pThreadData_->command = ThreadCommand::Nothing; + pThreadData_->status = ThreadStatus::Running; + + std::vector arguments; + for(const auto& iter: args) { + StoredObject store(iter.get()); + arguments.push_back(store.unpack(sol::this_state{pThreadData_->luaState})); + } + + pThread_.reset(new std::thread(&LuaThread::work, pThreadData_, function, std::move(arguments))); + ASSERT(pThread_.get() != nullptr); + pThread_->detach(); +} + +void LuaThread::luaHook(lua_State*, lua_Debug*) +{ + if (pThreadLocalData) + { + switch (pThreadLocalData->command) + { + case ThreadCommand::Pause: + { + pThreadLocalData->status = ThreadStatus::Paused; + ThreadCommand cmd = pThreadLocalData->command; + while (cmd == ThreadCommand::Pause) { + std::this_thread::yield(); + cmd = pThreadLocalData->command; + } + assert(cmd != ThreadCommand::Nothing); + if (cmd == ThreadCommand::Resume) + { + pThreadLocalData->status = ThreadStatus::Running; + break; // Just go out of the function + } + else { /* HOOK_STOP - do nothing and go to the next case */} + } + case ThreadCommand::Cancel: + throw LuaHookStopException(); + default: + case ThreadCommand::Nothing: + break; + } + } +} + +void LuaThread::work(std::shared_ptr threadData, const std::string strFunction, std::vector&& arguments) noexcept { + try { + pThreadLocalData = threadData.get(); + ASSERT(threadData.get()) << "invalid internal thread state\n"; + const sol::object& stringLoader = threadData->luaState["loadstring"]; + ASSERT(stringLoader.valid() && stringLoader.get_type() == sol::type::function); + sol::function userFuncObj = static_cast(stringLoader)(strFunction); + sol::function_result results = userFuncObj(sol::as_args(arguments)); + (void)results; // TODO: try to avoid use of useless sol::function_result here + sol::variadic_args args(threadData->luaState, -lua_gettop(threadData->luaState)); + for(const auto& iter: args) { + StoredObject store(iter.get()); + threadData->results.emplace_back(std::move(store)); + } + threadData->status = ThreadStatus::Completed; + } + catch (const LuaHookStopException&) { + threadData->status = ThreadStatus::Canceled; + } + catch (const sol::error& err) { + threadData->status = ThreadStatus::Failed; + sol::stack::push(threadData->luaState, err.what()); + StoredObject store(sol::stack::pop(threadData->luaState)); + threadData->results.emplace_back(std::move(store)); + } +} + +void LuaThread::cancel() noexcept +{ + pThreadData_->command = ThreadCommand::Cancel; +} + +void LuaThread::pause() noexcept +{ + pThreadData_->command = ThreadCommand::Pause; +} + +void LuaThread::resume() noexcept +{ + pThreadData_->command = ThreadCommand::Resume; +} + +std::tuple LuaThread::wait(sol::this_state state) const noexcept +{ + + ThreadStatus stat = pThreadData_->status; + while (stat == ThreadStatus::Running) { + std::this_thread::yield(); + stat = pThreadData_->status; + } + sol::table returns = sol::state_view(state).create_table(); + if (stat == ThreadStatus::Completed) + { + for (const StoredObject& obj: pThreadData_->results) + { + returns.add(obj.unpack(state)); + } + } + return std::make_tuple(sol::make_object(state, threadStatusToString(stat)), std::move(returns)); +} + +std::string LuaThread::threadStatusToString(ThreadStatus stat) const noexcept +{ + switch(stat) + { + case ThreadStatus::Running: return "running"; + case ThreadStatus::Paused: return "paused"; + case ThreadStatus::Canceled: return "canceled"; + case ThreadStatus::Completed: return "completed"; + case ThreadStatus::Failed: return "failed"; + } + assert(false); + return "unknown"; +} + +std::string LuaThread::status() const noexcept +{ + return threadStatusToString(pThreadData_->status); +} + sol::object LuaThread::getUserType(sol::state_view &lua) noexcept { static sol::usertype type( - sol::call_construction(), sol::constructors>(), - "join", &LuaThread::join, - "detach", &LuaThread::detach, - "thread_id", &LuaThread::threadId + "new", sol::no_constructor, + "cancel", &LuaThread::cancel, + "pause", &LuaThread::pause, + "resume", &LuaThread::resume, + "status", &LuaThread::status, + "wait", &LuaThread::wait + ); + sol::stack::push(lua, type); + return sol::stack::pop(lua); +} + +// class ThreadFactory + +ThreadFactory::ThreadFactory(const sol::function& func) : stepwise_(false), step_(100U) { + sol::state_view lua(func.lua_state()); + const sol::object& dumper = lua["string"]["dump"]; + ASSERT(dumper.valid() && dumper.get_type() == sol::type::function) + << "Unable to get string.dump()"; + strFunction_ = static_cast(dumper)(func); + + // Inherit all pathes from parent state by default + packagePath_ = lua["package"]["path"].get(); + packageCPath_ = lua["package"]["cpath"].get(); +} + +std::unique_ptr ThreadFactory::runThread(const sol::variadic_args& args) { + std::shared_ptr threadData(new LuaThread::ThreadData); + ASSERT(threadData.get()); + threadData->luaState.open_libraries( + sol::lib::base, sol::lib::string, + sol::lib::package, sol::lib::io, sol::lib::os + ); + + if (stepwise_) + lua_sethook(threadData->luaState, LuaThread::luaHook, LUA_MASKCOUNT, step_); + + threadData->luaState["package"]["path"] = packagePath_; + threadData->luaState["package"]["cpath"] = packageCPath_; + + // Inherit all pathes from parent state + effil::LuaThread::getUserType(threadData->luaState); + effil::ThreadFactory::getUserType(threadData->luaState); + effil::SharedTable::getUserType(threadData->luaState); + + return std::make_unique(threadData, strFunction_, args); +} + +bool ThreadFactory::stepwise(const sol::optional& value) +{ + bool ret = stepwise_ ; + if (value) + stepwise_ = value.value(); + return ret; +} + +unsigned int ThreadFactory::step(const sol::optional& value) +{ + bool ret = step_; + if (value) + step_ = value.value(); + return ret; +} + +std::string ThreadFactory::packagePath(const sol::optional& value) +{ + std::string& ret = packagePath_; + if (value) + packagePath_ = value.value(); + return ret; +} + +std::string ThreadFactory::packageCPath(const sol::optional& value) +{ + std::string& ret = packageCPath_; + if (value) + packageCPath_ = value.value(); + return ret; +} + +sol::object ThreadFactory::getUserType(sol::state_view &lua) noexcept +{ + static sol::usertype type( + "new", sol::no_constructor, + sol::meta_function::call, &ThreadFactory::runThread, + "stepwise", &ThreadFactory::stepwise, + "step", &ThreadFactory::step, + "package_path", &ThreadFactory::packagePath, + "package_cpath", &ThreadFactory::packageCPath ); sol::stack::push(lua, type); return sol::stack::pop(lua); diff --git a/src/cpp/threading.h b/src/cpp/threading.h index f685e59..ffd0ff5 100644 --- a/src/cpp/threading.h +++ b/src/cpp/threading.h @@ -8,24 +8,78 @@ namespace effil { +// Lua this thread API +std::string threadId() noexcept; +void yield() noexcept; +void sleep(int64_t, sol::optional) noexcept; + class LuaThread { public: - LuaThread(const sol::function& function, const sol::variadic_args& args) noexcept; - virtual ~LuaThread() noexcept = default; - void join() noexcept; - void detach() noexcept; + enum class ThreadStatus { + Running, + Paused, + Canceled, + Completed, + Failed, + }; - static std::string threadId() noexcept; + enum class ThreadCommand + { + Nothing = 1, + Cancel, + Pause, + Resume, + }; + + struct ThreadData{ + sol::state luaState; + std::atomic status; + std::atomic command; + std::vector results; + }; + + LuaThread(std::shared_ptr threadData, const std::string& function, const sol::variadic_args& args); static sol::object getUserType(sol::state_view &lua) noexcept; + static void luaHook(lua_State*, lua_Debug*); + + /* Public lua methods*/ + void cancel() noexcept; + void pause() noexcept; + void resume() noexcept; + std::string status() const noexcept; + std::tuple wait(sol::this_state state) const noexcept; private: - void work() noexcept; - void storeArgs(const sol::variadic_args &args) noexcept; + LuaThread(const LuaThread&) = delete; + LuaThread& operator=(const LuaThread&) = delete; - std::string str_function_; - std::shared_ptr p_state_; - std::shared_ptr p_thread_; - std::shared_ptr> p_arguments_; + std::string threadStatusToString(ThreadStatus stat) const noexcept; + static void work(std::shared_ptr threadData, const std::string strFunction, std::vector&& arguments) noexcept; + + std::shared_ptr pThreadData_; + std::shared_ptr pThread_; + + static thread_local LuaThread::ThreadData* pThreadLocalData; +}; + +class ThreadFactory { +public: + ThreadFactory(const sol::function& func); + static sol::object getUserType(sol::state_view &lua) noexcept; + + /* Public lua methods*/ + std::unique_ptr runThread(const sol::variadic_args& args); + bool stepwise(const sol::optional&); + unsigned int step(const sol::optional&); + std::string packagePath(const sol::optional&); + std::string packageCPath(const sol::optional&); + +private: + std::string strFunction_; + bool stepwise_; + unsigned int step_; + std::string packagePath_; + std::string packageCPath_; }; } // effil diff --git a/tests/lua/run_tests.lua b/tests/lua/run_tests.lua index d2a1704..c16f635 100755 --- a/tests/lua/run_tests.lua +++ b/tests/lua/run_tests.lua @@ -1,11 +1,57 @@ #!/usr/bin/env lua -- TODO: remove hardcode -package.path = package.path .. ";../libs/luaunit/?.lua" +package.path = package.path .. ";../libs/luaunit/?.lua;../tests/lua/?.lua" package.cpath = package.cpath .. ";./?.so;./?.dylib" test = require "luaunit" +function log(...) + local msg = '@\t\t' .. os.date('%Y-%m-%d %H:%M:%S ',os.time()) + for _, val in ipairs({...}) do + msg = msg .. tostring(val) .. ' ' + end + io.write(msg .. '\n') + io.flush() +end + +function wait(timeInSec, condition, silent) + if not silent then + log("Start waiting for " .. tostring(timeInSec) .. "sec...") + end + local result = false + local startTime = os.time() + while ( (os.time() - startTime) <= timeInSec) do + if condition ~= nil then + if type(condition) == 'function' then + if condition() then + result = true + break + end + else + if condition then + result = true + break + end + end + end + end + if not silent then + log "Give up" + end + return result +end + +function sleep(timeInSec, silent) + if not silent then + log("Start sleep for " .. tostring(timeInSec) .. "sec...") + end + wait(timeInSec, nil, true) + if not silent then + log "Wake up" + end +end + do -- Hack input arguments to make tests verbose by default local found = false @@ -21,37 +67,6 @@ do end end -function log(...) - local msg = '@\t\t' .. os.date('%Y-%m-%d %H:%M:%S ',os.time()) - for _, val in ipairs({...}) do - msg = msg .. tostring(val) .. ' ' - end - io.write(msg .. '\n') - io.flush() -end - -function wait(timeInSec, condition) - local startTime = os.time() - while ( (os.time() - startTime) <= timeInSec) do - if condition ~= nil then - if type(condition) == 'function' then - if condition() then - return true - end - else - if condition then - return true - end - end - end - end - return false -end - -function sleep(timeInSec) - wait(timeInMsec, nil) -end - ----------- -- TESTS -- ----------- diff --git a/tests/lua/smoke_test.lua b/tests/lua/smoke_test.lua index 961c44d..7c1172f 100644 --- a/tests/lua/smoke_test.lua +++ b/tests/lua/smoke_test.lua @@ -1,52 +1,220 @@ TestSmoke = {} -function TestSmoke:testGeneralWorkability() +function TestSmoke:tearDown() + log "TearDown() collect garbage" + collectgarbage() +end + +function TestSmoke:testSharedTableTypes() local effil = require('libeffil') local share = effil.share() share["number"] = 100500 share["string"] = "string value" share["bool"] = true + share["function"] = function(left, right) return left + right end - log "Start thread" - local thread = effil.thread( + local thread_factory = effil.thread( function(share) - share["child.number"] = share["number"] - share["child.string"] = share["string"] - share["child.bool"] = share["bool"] - end, - share + share["child.number"] = share["number"] + share["child.string"] = share["string"] + share["child.bool"] = share["bool"] + share["child.function"] = share["function"](11,45) + end ) - log "Join thread" - thread:join() + local thread = thread_factory(share) + thread:wait() log "Check values" - test.assertEquals(share["child.number"], share["number"], - "'number' fields are not equal") - test.assertEquals(share["child.string"], share["string"], - "'string' fields are not equal") - test.assertEquals(share["child.bool"], share["bool"], - "'bool' fields are not equal") + test.assertEquals(share["child.number"], share["number"]) + test.assertEquals(share["child.string"], share["string"]) + test.assertEquals(share["child.bool"], share["bool"]) + test.assertEquals(share["child.function"], share["function"](11,45)) end -function TestSmoke:testDetach() +function TestSmoke:testThreadCancel() + local effil = require('libeffil') + local thread_runner = effil.thread( + function() + local startTime = os.time() + while ( (os.time() - startTime) <= 10) do --[[ Just sleep ]] end + end + ) + test.assertFalse(thread_runner:stepwise(true)) + local thread = thread_runner() + sleep(2) -- let thread starts working + thread:cancel() + + test.assertTrue(wait(2, function() return thread:status() ~= 'running' end)) + test.assertEquals(thread:status(), 'canceled') +end + +function TestSmoke:testThreadPauseAndResume() + local effil = require('libeffil') + local data = effil.share() + data.value = 0 + local thread_runner = effil.thread( + function(data) + while true do + data.value = data.value + 1 + end + end + ) + test.assertFalse(thread_runner:stepwise(true)) + + local thread = thread_runner(data) + test.assertTrue(wait(2, function() return data.value > 100 end)) + thread:pause() + test.assertTrue(wait(2, function() return thread:status() == "paused" end)) + local savedValue = data.value + sleep(3) + test.assertEquals(data.value, savedValue) + + thread:resume() + test.assertTrue(wait(5, function() return (data.value - savedValue) > 100 end)) + thread:cancel() + thread:wait() +end + +function TestSmoke:testThreadPauseAndStop() + local effil = require('libeffil') + log "Create thread" + local data = effil.share() + data.value = 0 + local thread_runner = effil.thread( + function(data) + while true do + data.value = data.value + 1 + end + end + ) + test.assertFalse(thread_runner:stepwise(true)) + + local thread = thread_runner(data) + test.assertTrue(wait(2, function() return data.value > 100 end)) + thread:pause() + test.assertTrue(wait(2, function() return thread:status() == "paused" end)) + local savedValue = data.value + sleep(3) + test.assertEquals(data.value, savedValue) + + thread:cancel() + test.assertTrue(wait(2, function() return thread:status() == "canceled" end)) + thread:wait() +end + +function TestSmoke:testThreadPauseAndStop() + local effil = require('libeffil') + log "Create thread" + local data = effil.share() + data.value = 0 + local thread_runner = effil.thread( + function(data) + while true do + data.value = data.value + 1 + end + end + ) + test.assertFalse(thread_runner:stepwise(true)) + + local thread = thread_runner(data) + test.assertTrue(wait(2, function() return data.value > 100 end)) + thread:pause() + test.assertTrue(wait(2, function() return thread:status() == "paused" end)) + local savedValue = data.value + sleep(3) + test.assertEquals(data.value, savedValue) + + thread:cancel() + test.assertTrue(wait(2, function() return thread:status() == "canceled" end)) + thread:wait() +end + +function TestSmoke:testRecursiveTables() local effil = require('libeffil') local share = effil.share() - share["finished"] = false - log "Start thread" - local thread = effil.thread( - function(share) - local startTime = os.time() - while ( (os.time() - startTime) <= 3) do --[[ Like we are working 3sec ... ]] end - share["finished"] = true - end, - share - ) - log "Detach thread" - thread:detach() + local magic_number = 42 + share["subtable1"] = effil.share() + share["subtable1"]["subtable1"] = effil.share() + share["subtable1"]["subtable2"] = share["subtable1"]["subtable1"] + share["subtable2"] = share["subtable1"]["subtable1"] + share["magic_number"] = magic_number - log "Waiting for thread completion..." - test.assertEquals(wait(4, function() return share["finished"] end) , true) - log "Stop waiting" + local thread_factory = effil.thread( + function(share) + share["subtable1"]["subtable1"]["magic_number"] = share["magic_number"] + share["magic_number"] = nil + end + ) + local thread = thread_factory(share) + thread:wait() + + log "Check values" + test.assertEquals(share["subtable1"]["subtable1"]["magic_number"], magic_number) + test.assertEquals(share["subtable1"]["subtable2"]["magic_number"], magic_number) + test.assertEquals(share["subtable2"]["magic_number"], magic_number) + test.assertEquals(share["magic_number"], nil) end + +function TestSmoke:testThisThreadFunctions() + local effil = require('libeffil') + local share = effil.share() + + local thread_factory = effil.thread( + function(share) + share["child.id"] = require('libeffil').thread_id() + end + ) + local thread = thread_factory(share) + thread:wait() + + log "Check values" + test.assertString(share["child.id"]) + test.assertNumber(tonumber(share["child.id"])) + test.assertNotEquals(share["child.id"], effil.thread_id()) + effil.yield() -- just call it + + local function check_time(real_time, use_time, metric) + local start_time = os.time() + effil.sleep(use_time, metric) + test.assertAlmostEquals(os.time(), start_time + real_time, 1) + end + check_time(4, 4, nil) -- seconds by default + check_time(4, 4, 's') + check_time(4, 4000, 'ms') + check_time(60, 1, 'm') +end + +function TestSmoke:testCheckThreadReturns() + local effil = require('libeffil') + local share = effil.share() + share.value = "some value" + + local thread_factory = effil.thread( + function(share) + return 100500, "string value", true, share, function(a,b) return a + b end + end + ) + local thread = thread_factory(share) + local status, returns = thread:wait() + + log "Check values" + test.assertEquals(status, "completed") + + test.assertNumber(returns[1]) + test.assertEquals(returns[1], 100500) + + test.assertString(returns[2]) + test.assertEquals(returns[2], "string value") + + test.assertBoolean(returns[3]) + test.assertTrue(returns[3]) + + test.assertUserdata(returns[4]) + test.assertEquals(returns[4].value, share.value) + + test.assertFunction(returns[5]) + test.assertEquals(returns[5](11, 89), 100) +end +