Compare commits

...

10 Commits

11 changed files with 287 additions and 23 deletions

View File

@ -17,6 +17,7 @@ target_sources(nf7_core
luajit/lambda.hh luajit/lambda.hh
luajit/thread.hh luajit/thread.hh
clock.hh clock.hh
logger.hh
version.hh version.hh
) )

29
core/logger.hh Normal file
View 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

View File

@ -14,13 +14,10 @@
#include "iface/common/exception.hh" #include "iface/common/exception.hh"
#include "iface/common/task.hh" #include "iface/common/task.hh"
#include "iface/subsys/clock.hh"
#include "iface/subsys/concurrency.hh" #include "iface/subsys/concurrency.hh"
#include "iface/subsys/parallelism.hh" #include "iface/subsys/parallelism.hh"
#include "iface/env.hh" #include "iface/env.hh"
#include "core/clock.hh"
namespace nf7::core::luajit::test { namespace nf7::core::luajit::test {
class ContextFixture : public ::testing::TestWithParam<Context::Kind> { class ContextFixture : public ::testing::TestWithParam<Context::Kind> {
@ -97,7 +94,6 @@ class ContextFixture : public ::testing::TestWithParam<Context::Kind> {
void SetUp() override { void SetUp() override {
syncq_ = std::make_shared<SimpleTaskQueue<SyncTask>>(); syncq_ = std::make_shared<SimpleTaskQueue<SyncTask>>();
asyncq_ = std::make_shared<SimpleTaskQueue<AsyncTask>>(); asyncq_ = std::make_shared<SimpleTaskQueue<AsyncTask>>();
clock_ = std::make_shared<Clock>();
env_.emplace(SimpleEnv::FactoryMap { env_.emplace(SimpleEnv::FactoryMap {
{ {
@ -112,11 +108,6 @@ class ContextFixture : public ::testing::TestWithParam<Context::Kind> {
WrappedTaskQueue<subsys::Parallelism>>(asyncq_); WrappedTaskQueue<subsys::Parallelism>>(asyncq_);
}, },
}, },
{
typeid(subsys::Clock), [this](auto&) {
return clock_;
},
},
{ {
typeid(Context), [this](auto& env) { typeid(Context), [this](auto& env) {
return Context::Create(env, GetParam()); return Context::Create(env, GetParam());
@ -156,7 +147,6 @@ class ContextFixture : public ::testing::TestWithParam<Context::Kind> {
protected: protected:
std::shared_ptr<SimpleTaskQueue<SyncTask>> syncq_; std::shared_ptr<SimpleTaskQueue<SyncTask>> syncq_;
std::shared_ptr<SimpleTaskQueue<AsyncTask>> asyncq_; std::shared_ptr<SimpleTaskQueue<AsyncTask>> asyncq_;
std::shared_ptr<Clock> clock_;
std::optional<SimpleEnv> env_; std::optional<SimpleEnv> env_;
private: private:

View File

@ -30,6 +30,10 @@ class Lambda::Thread : public luajit::Thread {
} }
void onAborted(TaskContext& lua) noexcept override { void onAborted(TaskContext& lua) noexcept override {
if (auto la = la_.lock()) { if (auto la = la_.lock()) {
if (auto logger = la->logger_) {
const auto msg = lua_tostring(*lua, -1);
logger->Error(msg);
}
++la->abort_count_; ++la->abort_count_;
TryResume(lua, la); TryResume(lua, la);
} }
@ -138,9 +142,14 @@ void Lambda::PushLuaContextObject(TaskContext& lua) noexcept {
lua_pushcfunction(*lua, [](auto L) { lua_pushcfunction(*lua, [](auto L) {
const auto la = self(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 wla = std::weak_ptr<Lambda> {la};
const auto dur = luaL_checkinteger(L, 2); const auto dur = luaL_checkinteger(L, 2);
const auto after = la->clock_->now() + dur * 1ms; const auto after = la->clock_->now() + dur * 1ms;
la->lua_->Push(Task {after, [wla](auto& lua) { la->lua_->Push(Task {after, [wla](auto& lua) {
if (auto la = wla.lock()) { if (auto la = wla.lock()) {
assert(la->thread_); assert(la->thread_);
@ -150,6 +159,32 @@ void Lambda::PushLuaContextObject(TaskContext& lua) noexcept {
return lua_yield(L, 0); return lua_yield(L, 0);
}); });
lua_setfield(*lua, -2, "sleep"); 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"); lua_setfield(*lua, -2, "__index");
} }

View File

@ -9,11 +9,13 @@
#include "iface/subsys/clock.hh" #include "iface/subsys/clock.hh"
#include "iface/subsys/concurrency.hh" #include "iface/subsys/concurrency.hh"
#include "iface/subsys/logger.hh"
#include "iface/env.hh" #include "iface/env.hh"
#include "iface/lambda.hh" #include "iface/lambda.hh"
#include "core/luajit/context.hh" #include "core/luajit/context.hh"
#include "core/luajit/thread.hh" #include "core/luajit/thread.hh"
#include "core/logger.hh"
namespace nf7::core::luajit { namespace nf7::core::luajit {
@ -23,8 +25,9 @@ class Lambda :
public: public:
explicit Lambda(nf7::Env& env, const std::shared_ptr<luajit::Value>& func) explicit Lambda(nf7::Env& env, const std::shared_ptr<luajit::Value>& func)
: LambdaBase(), : LambdaBase(),
clock_(env.Get<subsys::Clock>()), clock_(env.GetOr<subsys::Clock>()),
concurrency_(env.Get<subsys::Concurrency>()), concurrency_(env.Get<subsys::Concurrency>()),
logger_(env.GetOr<subsys::Logger>(NullLogger::instance())),
lua_(env.Get<luajit::Context>()), lua_(env.Get<luajit::Context>()),
func_(func) { } func_(func) { }
@ -43,6 +46,7 @@ class Lambda :
private: private:
const std::shared_ptr<subsys::Clock> clock_; const std::shared_ptr<subsys::Clock> clock_;
const std::shared_ptr<subsys::Concurrency> concurrency_; const std::shared_ptr<subsys::Concurrency> concurrency_;
const std::shared_ptr<subsys::Logger> logger_;
const std::shared_ptr<Context> lua_; const std::shared_ptr<Context> lua_;
const std::shared_ptr<Value> func_; const std::shared_ptr<Value> func_;

View File

@ -6,9 +6,13 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
#include "iface/subsys/clock.hh"
#include "core/luajit/context.hh" #include "core/luajit/context.hh"
#include "core/clock.hh"
#include "iface/common/observer_test.hh" #include "iface/common/observer_test.hh"
#include "iface/subsys/logger_test.hh"
#include "core/luajit/context_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, void Expect(const char* script,
const std::vector<nf7::Value>& in, const std::vector<nf7::Value>& in,
uint32_t expectExit = 1, uint32_t expectAbort = 0, 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 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) { for (const auto& v : in) {
sut->taker()->Take(v); sut->taker()->Take(v);
} }
@ -133,20 +142,71 @@ TEST_P(LuaJIT_Lambda, CtxMultiSend) {
} }
TEST_P(LuaJIT_Lambda, CtxSleep) { TEST_P(LuaJIT_Lambda, CtxSleep) {
clock_->Tick(); const auto clock = std::make_shared<nf7::core::Clock>();
const auto begin = clock_->now(); nf7::SimpleEnv env {{
{typeid(nf7::subsys::Clock), [&](auto&) { return clock; }},
}, *env_};
clock->Tick();
const auto begin = clock->now();
Expect( Expect(
"local ctx = ...\nctx:sleep(100)", "local ctx = ...\nctx:sleep(100)",
{nf7::Value {}}, {nf7::Value {}},
1, 0); 1, 0,
{},
&env);
clock_->Tick(); clock->Tick();
const auto end = clock_->now(); const auto end = clock->now();
EXPECT_GE(end-begin, 100ms); 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( INSTANTIATE_TEST_SUITE_P(
SyncOrAsync, LuaJIT_Lambda, SyncOrAsync, LuaJIT_Lambda,
testing::Values( testing::Values(

View File

@ -21,6 +21,7 @@ target_sources(nf7_iface
data/interface.hh data/interface.hh
subsys/concurrency.hh subsys/concurrency.hh
subsys/interface.hh subsys/interface.hh
subsys/logger.hh
subsys/parallelism.hh subsys/parallelism.hh
env.hh env.hh
file.hh file.hh
@ -40,6 +41,7 @@ target_sources(nf7_iface_test
common/task_test.cc common/task_test.cc
common/task_test.hh common/task_test.hh
common/value_test.cc common/value_test.cc
subsys/logger_test.hh
lambda_test.cc lambda_test.cc
lambda_test.hh lambda_test.hh
) )

View File

@ -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> template <typename I>
class SimpleContainer : public Container<I> { class SimpleContainer : public Container<I> {
public: public:
@ -95,8 +114,9 @@ class SimpleContainer : public Container<I> {
} }
SimpleContainer() = delete; SimpleContainer() = delete;
explicit SimpleContainer(FactoryMap&& factories) noexcept SimpleContainer(FactoryMap&& factories,
: factories_(std::move(factories)) { } Container<I>& fb = *NullContainer<I>::instance()) noexcept
: fallback_(fb), factories_(std::move(factories)) { }
std::shared_ptr<I> Get(std::type_index idx) override { std::shared_ptr<I> Get(std::type_index idx) override {
const auto obj_itr = objs_.find(idx); const auto obj_itr = objs_.find(idx);
@ -122,11 +142,11 @@ class SimpleContainer : public Container<I> {
throw Exception {"memory shortage"}; throw Exception {"memory shortage"};
} }
} }
return nullptr; return fallback_.Get(idx);
} }
bool installed(std::type_index idx) const noexcept override { bool installed(std::type_index idx) const noexcept override {
return factories_.contains(idx); return factories_.contains(idx) || fallback_.installed(idx);
} }
using Container<I>::Get; using Container<I>::Get;
@ -134,6 +154,8 @@ class SimpleContainer : public Container<I> {
using Container<I>::installed; using Container<I>::installed;
private: private:
Container<I>& fallback_;
FactoryMap factories_; FactoryMap factories_;
ObjectMap objs_; ObjectMap objs_;

View File

@ -66,6 +66,28 @@ TEST(SimpleContainer, CheckInstalled) {
EXPECT_FALSE(sut.installed<IB>()); 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) #if !defined(NDEBUG)
TEST(SimpleContainer, DeathByFetchRecursive) { TEST(SimpleContainer, DeathByFetchRecursive) {
SUT sut {{ SUT sut {{

80
iface/subsys/logger.hh Normal file
View 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

View 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