Compare commits
	
		
			10 Commits
		
	
	
		
			d0f0955c47
			...
			c2fb269d40
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c2fb269d40 | |||
| 8a783c87a4 | |||
| 281f70adb4 | |||
| a3043963da | |||
| aa73fb50a5 | |||
| 782171268a | |||
| 1aa14f541e | |||
| 295bb7c5f0 | |||
| 32fc72824b | |||
| e6d9d2205d | 
| @@ -17,6 +17,7 @@ target_sources(nf7_core | ||||
|     luajit/lambda.hh | ||||
|     luajit/thread.hh | ||||
|     clock.hh | ||||
|     logger.hh | ||||
|     version.hh | ||||
| ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										29
									
								
								core/logger.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								core/logger.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| // No copyright | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
|  | ||||
| #include "iface/common/exception.hh" | ||||
| #include "iface/subsys/logger.hh" | ||||
|  | ||||
|  | ||||
| namespace nf7::core { | ||||
|  | ||||
| class NullLogger : public subsys::Logger { | ||||
|  public: | ||||
|   static const std::shared_ptr<NullLogger>& instance() | ||||
|   try { | ||||
|     static const auto kInstance = std::make_shared<NullLogger>(); | ||||
|     return kInstance; | ||||
|   } catch (const std::bad_alloc&) { | ||||
|     throw Exception {"memory shortage"}; | ||||
|   } | ||||
|  | ||||
|  public: | ||||
|   NullLogger() noexcept : subsys::Logger("nf7::core::NullLogger") { } | ||||
|  | ||||
|  public: | ||||
|   void Push(const Item&) noexcept override { } | ||||
| }; | ||||
|  | ||||
| }  // namespace nf7::core | ||||
| @@ -14,13 +14,10 @@ | ||||
|  | ||||
| #include "iface/common/exception.hh" | ||||
| #include "iface/common/task.hh" | ||||
| #include "iface/subsys/clock.hh" | ||||
| #include "iface/subsys/concurrency.hh" | ||||
| #include "iface/subsys/parallelism.hh" | ||||
| #include "iface/env.hh" | ||||
|  | ||||
| #include "core/clock.hh" | ||||
|  | ||||
| namespace nf7::core::luajit::test { | ||||
|  | ||||
| class ContextFixture : public ::testing::TestWithParam<Context::Kind> { | ||||
| @@ -97,7 +94,6 @@ class ContextFixture : public ::testing::TestWithParam<Context::Kind> { | ||||
|   void SetUp() override { | ||||
|     syncq_  = std::make_shared<SimpleTaskQueue<SyncTask>>(); | ||||
|     asyncq_ = std::make_shared<SimpleTaskQueue<AsyncTask>>(); | ||||
|     clock_  = std::make_shared<Clock>(); | ||||
|  | ||||
|     env_.emplace(SimpleEnv::FactoryMap { | ||||
|       { | ||||
| @@ -112,11 +108,6 @@ class ContextFixture : public ::testing::TestWithParam<Context::Kind> { | ||||
|               WrappedTaskQueue<subsys::Parallelism>>(asyncq_); | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         typeid(subsys::Clock), [this](auto&) { | ||||
|           return clock_; | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         typeid(Context), [this](auto& env) { | ||||
|           return Context::Create(env, GetParam()); | ||||
| @@ -156,7 +147,6 @@ class ContextFixture : public ::testing::TestWithParam<Context::Kind> { | ||||
|  protected: | ||||
|   std::shared_ptr<SimpleTaskQueue<SyncTask>> syncq_; | ||||
|   std::shared_ptr<SimpleTaskQueue<AsyncTask>> asyncq_; | ||||
|   std::shared_ptr<Clock> clock_; | ||||
|   std::optional<SimpleEnv> env_; | ||||
|  | ||||
|  private: | ||||
|   | ||||
| @@ -30,6 +30,10 @@ class Lambda::Thread : public luajit::Thread { | ||||
|   } | ||||
|   void onAborted(TaskContext& lua) noexcept override { | ||||
|     if (auto la = la_.lock()) { | ||||
|       if (auto logger = la->logger_) { | ||||
|         const auto msg = lua_tostring(*lua, -1); | ||||
|         logger->Error(msg); | ||||
|       } | ||||
|       ++la->abort_count_; | ||||
|       TryResume(lua, la); | ||||
|     } | ||||
| @@ -138,9 +142,14 @@ void Lambda::PushLuaContextObject(TaskContext& lua) noexcept { | ||||
|  | ||||
|       lua_pushcfunction(*lua, [](auto L) { | ||||
|         const auto la = self(L); | ||||
|         if (nullptr == la->clock_) { | ||||
|           return luaL_error(L, "clock is not installed in the environment"); | ||||
|         } | ||||
|  | ||||
|         const auto wla   = std::weak_ptr<Lambda> {la}; | ||||
|         const auto dur   = luaL_checkinteger(L, 2); | ||||
|         const auto after = la->clock_->now() + dur * 1ms; | ||||
|  | ||||
|         la->lua_->Push(Task {after, [wla](auto& lua) { | ||||
|           if (auto la = wla.lock()) { | ||||
|             assert(la->thread_); | ||||
| @@ -150,6 +159,32 @@ void Lambda::PushLuaContextObject(TaskContext& lua) noexcept { | ||||
|         return lua_yield(L, 0); | ||||
|       }); | ||||
|       lua_setfield(*lua, -2, "sleep"); | ||||
|  | ||||
|       static const auto logFunc = []<subsys::Logger::Level lv>(auto L) { | ||||
|         const auto la       = self(L); | ||||
|         const auto contents = luaL_checkstring(L, 2); | ||||
|         la->logger_->Push(subsys::Logger::Item {lv, contents}); | ||||
|         return 0; | ||||
|       }; | ||||
|       lua_pushcfunction(*lua, [](auto L) { | ||||
|         return logFunc.operator()<subsys::Logger::kTrace>(L); | ||||
|       }); | ||||
|       lua_setfield(*lua, -2, "trace"); | ||||
|  | ||||
|       lua_pushcfunction(*lua, [](auto L) { | ||||
|         return logFunc.operator()<subsys::Logger::kInfo>(L); | ||||
|       }); | ||||
|       lua_setfield(*lua, -2, "info"); | ||||
|  | ||||
|       lua_pushcfunction(*lua, [](auto L) { | ||||
|         return logFunc.operator()<subsys::Logger::kWarn>(L); | ||||
|       }); | ||||
|       lua_setfield(*lua, -2, "warn"); | ||||
|  | ||||
|       lua_pushcfunction(*lua, [](auto L) { | ||||
|         return logFunc.operator()<subsys::Logger::kError>(L); | ||||
|       }); | ||||
|       lua_setfield(*lua, -2, "error"); | ||||
|     } | ||||
|     lua_setfield(*lua, -2, "__index"); | ||||
|   } | ||||
|   | ||||
| @@ -9,11 +9,13 @@ | ||||
|  | ||||
| #include "iface/subsys/clock.hh" | ||||
| #include "iface/subsys/concurrency.hh" | ||||
| #include "iface/subsys/logger.hh" | ||||
| #include "iface/env.hh" | ||||
| #include "iface/lambda.hh" | ||||
|  | ||||
| #include "core/luajit/context.hh" | ||||
| #include "core/luajit/thread.hh" | ||||
| #include "core/logger.hh" | ||||
|  | ||||
| namespace nf7::core::luajit { | ||||
|  | ||||
| @@ -23,8 +25,9 @@ class Lambda : | ||||
|  public: | ||||
|   explicit Lambda(nf7::Env& env, const std::shared_ptr<luajit::Value>& func) | ||||
|       : LambdaBase(), | ||||
|         clock_(env.Get<subsys::Clock>()), | ||||
|         clock_(env.GetOr<subsys::Clock>()), | ||||
|         concurrency_(env.Get<subsys::Concurrency>()), | ||||
|         logger_(env.GetOr<subsys::Logger>(NullLogger::instance())), | ||||
|         lua_(env.Get<luajit::Context>()), | ||||
|         func_(func) { } | ||||
|  | ||||
| @@ -43,6 +46,7 @@ class Lambda : | ||||
|  private: | ||||
|   const std::shared_ptr<subsys::Clock>       clock_; | ||||
|   const std::shared_ptr<subsys::Concurrency> concurrency_; | ||||
|   const std::shared_ptr<subsys::Logger>      logger_; | ||||
|  | ||||
|   const std::shared_ptr<Context> lua_; | ||||
|   const std::shared_ptr<Value>   func_; | ||||
|   | ||||
| @@ -6,9 +6,13 @@ | ||||
| #include <optional> | ||||
| #include <vector> | ||||
|  | ||||
| #include "iface/subsys/clock.hh" | ||||
|  | ||||
| #include "core/luajit/context.hh" | ||||
| #include "core/clock.hh" | ||||
|  | ||||
| #include "iface/common/observer_test.hh" | ||||
| #include "iface/subsys/logger_test.hh" | ||||
|  | ||||
| #include "core/luajit/context_test.hh" | ||||
|  | ||||
| @@ -36,10 +40,15 @@ class LuaJIT_Lambda : public nf7::core::luajit::test::ContextFixture { | ||||
|   void Expect(const char* script, | ||||
|               const std::vector<nf7::Value>& in, | ||||
|               uint32_t expectExit = 1, uint32_t expectAbort = 0, | ||||
|               const std::vector<nf7::Value>& out = {}) { | ||||
|               const std::vector<nf7::Value>& out = {}, | ||||
|               nf7::Env* env = nullptr) { | ||||
|     if (nullptr == env) { | ||||
|       env = &*env_; | ||||
|     } | ||||
|  | ||||
|     auto func = Compile(script); | ||||
|  | ||||
|     auto sut = std::make_shared<nf7::core::luajit::Lambda>(*env_, func); | ||||
|     auto sut = std::make_shared<nf7::core::luajit::Lambda>(*env, func); | ||||
|     for (const auto& v : in) { | ||||
|       sut->taker()->Take(v); | ||||
|     } | ||||
| @@ -133,20 +142,71 @@ TEST_P(LuaJIT_Lambda, CtxMultiSend) { | ||||
| } | ||||
|  | ||||
| TEST_P(LuaJIT_Lambda, CtxSleep) { | ||||
|   clock_->Tick(); | ||||
|   const auto begin = clock_->now(); | ||||
|   const auto clock = std::make_shared<nf7::core::Clock>(); | ||||
|   nf7::SimpleEnv env {{ | ||||
|     {typeid(nf7::subsys::Clock), [&](auto&) { return clock; }}, | ||||
|   }, *env_}; | ||||
|  | ||||
|   clock->Tick(); | ||||
|   const auto begin = clock->now(); | ||||
|  | ||||
|   Expect( | ||||
|       "local ctx = ...\nctx:sleep(100)", | ||||
|       {nf7::Value {}}, | ||||
|       1, 0); | ||||
|       1, 0, | ||||
|       {}, | ||||
|       &env); | ||||
|  | ||||
|   clock_->Tick(); | ||||
|   const auto end = clock_->now(); | ||||
|   clock->Tick(); | ||||
|   const auto end = clock->now(); | ||||
|  | ||||
|   EXPECT_GE(end-begin, 100ms); | ||||
| } | ||||
|  | ||||
| TEST_P(LuaJIT_Lambda, CtxSleepWithoutClock) { | ||||
|   Expect( | ||||
|       "local ctx = ...\nctx:sleep(100)", | ||||
|       {nf7::Value {}}, | ||||
|       0, 1); | ||||
| } | ||||
|  | ||||
| TEST_P(LuaJIT_Lambda, CtxLogging) { | ||||
|   const auto logger = std::make_shared<nf7::subsys::test::LoggerMock>(); | ||||
|  | ||||
|   EXPECT_CALL(*logger, Push) | ||||
|       .WillOnce([](auto& item) { | ||||
|         EXPECT_EQ(item.level(), nf7::subsys::Logger::kTrace); | ||||
|         EXPECT_EQ(item.contents(), "this is trace"); | ||||
|       }) | ||||
|       .WillOnce([](auto& item) { | ||||
|         EXPECT_EQ(item.level(), nf7::subsys::Logger::kInfo); | ||||
|         EXPECT_EQ(item.contents(), "this is info"); | ||||
|       }) | ||||
|       .WillOnce([](auto& item) { | ||||
|         EXPECT_EQ(item.level(), nf7::subsys::Logger::kWarn); | ||||
|         EXPECT_EQ(item.contents(), "this is warn"); | ||||
|       }) | ||||
|       .WillOnce([](auto& item) { | ||||
|         EXPECT_EQ(item.level(), nf7::subsys::Logger::kError); | ||||
|         EXPECT_EQ(item.contents(), "this is error"); | ||||
|       }); | ||||
|  | ||||
|   nf7::SimpleEnv env {{ | ||||
|     {typeid(nf7::subsys::Logger), [&](auto&) { return logger; }}, | ||||
|   }, *env_}; | ||||
|  | ||||
|   Expect( | ||||
|       "local ctx = ...\n" | ||||
|       "ctx:trace(\"this is trace\")\n" | ||||
|       "ctx:info(\"this is info\")\n" | ||||
|       "ctx:warn(\"this is warn\")\n" | ||||
|       "ctx:error(\"this is error\")", | ||||
|       {nf7::Value {}}, | ||||
|       1, 0, | ||||
|       {}, | ||||
|       &env); | ||||
| } | ||||
|  | ||||
| INSTANTIATE_TEST_SUITE_P( | ||||
|     SyncOrAsync, LuaJIT_Lambda, | ||||
|     testing::Values( | ||||
|   | ||||
| @@ -21,6 +21,7 @@ target_sources(nf7_iface | ||||
|     data/interface.hh | ||||
|     subsys/concurrency.hh | ||||
|     subsys/interface.hh | ||||
|     subsys/logger.hh | ||||
|     subsys/parallelism.hh | ||||
|     env.hh | ||||
|     file.hh | ||||
| @@ -40,6 +41,7 @@ target_sources(nf7_iface_test | ||||
|     common/task_test.cc | ||||
|     common/task_test.hh | ||||
|     common/value_test.cc | ||||
|     subsys/logger_test.hh | ||||
|     lambda_test.cc | ||||
|     lambda_test.hh | ||||
| ) | ||||
|   | ||||
| @@ -64,6 +64,25 @@ class Container { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| template <typename I> | ||||
| class NullContainer : public Container<I> { | ||||
|  public: | ||||
|   static std::shared_ptr<NullContainer> instance() | ||||
|   try { | ||||
|     static const auto kInstance = std::make_shared<NullContainer>(); | ||||
|     return kInstance; | ||||
|   } catch (const std::bad_alloc&) { | ||||
|     throw Exception {"memory shortage"}; | ||||
|   } | ||||
|  | ||||
|  public: | ||||
|   NullContainer() = default; | ||||
|  | ||||
|  public: | ||||
|   std::shared_ptr<I> Get(std::type_index) override { return nullptr; } | ||||
|   bool installed(std::type_index) const noexcept override { return false; } | ||||
| }; | ||||
|  | ||||
| template <typename I> | ||||
| class SimpleContainer : public Container<I> { | ||||
|  public: | ||||
| @@ -95,8 +114,9 @@ class SimpleContainer : public Container<I> { | ||||
|   } | ||||
|  | ||||
|   SimpleContainer() = delete; | ||||
|   explicit SimpleContainer(FactoryMap&& factories) noexcept | ||||
|       : factories_(std::move(factories)) { } | ||||
|   SimpleContainer(FactoryMap&& factories, | ||||
|                   Container<I>& fb = *NullContainer<I>::instance()) noexcept | ||||
|       : fallback_(fb), factories_(std::move(factories)) { } | ||||
|  | ||||
|   std::shared_ptr<I> Get(std::type_index idx) override { | ||||
|     const auto obj_itr = objs_.find(idx); | ||||
| @@ -122,11 +142,11 @@ class SimpleContainer : public Container<I> { | ||||
|         throw Exception {"memory shortage"}; | ||||
|       } | ||||
|     } | ||||
|     return nullptr; | ||||
|     return fallback_.Get(idx); | ||||
|   } | ||||
|  | ||||
|   bool installed(std::type_index idx) const noexcept override { | ||||
|     return factories_.contains(idx); | ||||
|     return factories_.contains(idx) || fallback_.installed(idx); | ||||
|   } | ||||
|  | ||||
|   using Container<I>::Get; | ||||
| @@ -134,6 +154,8 @@ class SimpleContainer : public Container<I> { | ||||
|   using Container<I>::installed; | ||||
|  | ||||
|  private: | ||||
|   Container<I>& fallback_; | ||||
|  | ||||
|   FactoryMap factories_; | ||||
|   ObjectMap  objs_; | ||||
|  | ||||
|   | ||||
| @@ -66,6 +66,28 @@ TEST(SimpleContainer, CheckInstalled) { | ||||
|   EXPECT_FALSE(sut.installed<IB>()); | ||||
| } | ||||
|  | ||||
| TEST(SimpleContainer, FetchWithFallback) { | ||||
|   SUT fb {{ | ||||
|     SUT::MakePair<IA, A>(), | ||||
|   }}; | ||||
|   SUT sut {{}, fb}; | ||||
|   auto ptr = sut.Get<IA>(); | ||||
|   EXPECT_TRUE(std::dynamic_pointer_cast<A>(ptr)); | ||||
| } | ||||
| TEST(SimpleContainer, FetchUnknownWithFallback) { | ||||
|   SUT fb {{}}; | ||||
|   SUT sut {{}, fb}; | ||||
|   EXPECT_THROW(sut.Get<IA>(), nf7::Exception); | ||||
| } | ||||
| TEST(SimpleContainer, CheckInstalledWithFallback) { | ||||
|   SUT fb {{ | ||||
|     SUT::MakePair<IA, A>(), | ||||
|   }}; | ||||
|   SUT sut {{}, fb}; | ||||
|   EXPECT_TRUE(sut.installed<IA>()); | ||||
|   EXPECT_FALSE(sut.installed<IB>()); | ||||
| } | ||||
|  | ||||
| #if !defined(NDEBUG) | ||||
| TEST(SimpleContainer, DeathByFetchRecursive) { | ||||
|   SUT sut {{ | ||||
|   | ||||
							
								
								
									
										80
									
								
								iface/subsys/logger.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								iface/subsys/logger.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| // No copyright | ||||
| #pragma once | ||||
|  | ||||
| #include <source_location> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
|  | ||||
| #include "iface/subsys/interface.hh" | ||||
|  | ||||
|  | ||||
| namespace nf7::subsys { | ||||
|  | ||||
| class Logger : public Interface { | ||||
|  public: | ||||
|   using SrcLoc = std::source_location; | ||||
|  | ||||
|   enum Level { | ||||
|     kTrace, | ||||
|     kInfo, | ||||
|     kWarn, | ||||
|     kError, | ||||
|   }; | ||||
|  | ||||
|   class Item final { | ||||
|    public: | ||||
|     Item() = delete; | ||||
|     Item(Level level, | ||||
|          std::string_view contents, | ||||
|          std::source_location srcloc = std::source_location::current()) noexcept | ||||
|         : level_(level), contents_(contents), srcloc_(srcloc) { } | ||||
|  | ||||
|     Item(const Item&) = default; | ||||
|     Item(Item&&) = default; | ||||
|     Item& operator=(const Item&) = default; | ||||
|     Item& operator=(Item&&) = default; | ||||
|  | ||||
|     Level level() const noexcept { return level_; } | ||||
|     const std::string& contents() const noexcept { return contents_; } | ||||
|     const std::source_location& srcloc() const noexcept { return srcloc_; } | ||||
|  | ||||
|    private: | ||||
|     Level level_; | ||||
|     std::string contents_; | ||||
|     std::source_location srcloc_; | ||||
|   }; | ||||
|  | ||||
|  public: | ||||
|   using Interface::Interface; | ||||
|  | ||||
|  public: | ||||
|   // THREAD-SAFE | ||||
|   virtual void Push(const Item&) noexcept = 0; | ||||
|  | ||||
|   // THREAD-SAFE | ||||
|   void Trace(std::string_view contents, | ||||
|              SrcLoc srcloc = SrcLoc::current()) noexcept { | ||||
|     Push(Item {kTrace, contents, srcloc}); | ||||
|   } | ||||
|  | ||||
|   // THREAD-SAFE | ||||
|   void Info(std::string_view contents, | ||||
|             SrcLoc srcloc = SrcLoc::current()) noexcept { | ||||
|     Push(Item {kInfo, contents, srcloc}); | ||||
|   } | ||||
|  | ||||
|   // THREAD-SAFE | ||||
|   void Warn(std::string_view contents, | ||||
|             SrcLoc srcloc = SrcLoc::current()) noexcept { | ||||
|     Push(Item {kWarn, contents, srcloc}); | ||||
|   } | ||||
|  | ||||
|   // THREAD-SAFE | ||||
|   void Error(std::string_view contents, | ||||
|              SrcLoc srcloc = SrcLoc::current()) noexcept { | ||||
|     Push(Item {kError, contents, srcloc}); | ||||
|   } | ||||
| }; | ||||
|  | ||||
|  | ||||
| }  // namespace nf7::subsys | ||||
							
								
								
									
										19
									
								
								iface/subsys/logger_test.hh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								iface/subsys/logger_test.hh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| // No copyright | ||||
| #pragma once | ||||
|  | ||||
| #include "iface/subsys/logger.hh" | ||||
|  | ||||
| #include <gmock/gmock.h> | ||||
|  | ||||
|  | ||||
| namespace nf7::subsys::test { | ||||
|  | ||||
| class LoggerMock : public Logger { | ||||
|  public: | ||||
|   explicit LoggerMock(const char* name = "LoggerMock") noexcept | ||||
|       : Logger(name) {} | ||||
|  | ||||
|   MOCK_METHOD(void, Push, (const Item&), (noexcept)); | ||||
| }; | ||||
|  | ||||
| }  // namespace nf7::subsys::test | ||||
		Reference in New Issue
	
	Block a user