规范优化4
This commit is contained in:
parent
f5e5c2da1f
commit
1e5b964a7c
@ -15,6 +15,7 @@
|
||||
|
||||
#include "node.h"
|
||||
#include "plugin_loader.h"
|
||||
#include "utils/result.h"
|
||||
#include "utils/simple_json.h"
|
||||
|
||||
namespace rk3588 {
|
||||
@ -178,28 +179,40 @@ public:
|
||||
~GraphManager();
|
||||
|
||||
static bool LoadConfigFile(const std::string& path, SimpleJson& out, std::string& err);
|
||||
static Result<SimpleJson> LoadConfig(const std::string& path);
|
||||
|
||||
bool Build(const SimpleJson& root_cfg, std::string& err);
|
||||
bool BuildFromFile(const std::string& path, std::string& err);
|
||||
Status BuildFromConfig(const SimpleJson& root_cfg);
|
||||
Status BuildFromPath(const std::string& path);
|
||||
|
||||
bool StartAll();
|
||||
void StopAll();
|
||||
void RequestStop();
|
||||
void BlockUntilStop();
|
||||
|
||||
bool ReloadFromFile(const std::string& path, std::string& err);
|
||||
Status Reload(const std::string& path);
|
||||
|
||||
const std::string& ConfigPath() const { return config_path_; }
|
||||
const std::string& LastGoodPath() const { return last_good_path_; }
|
||||
bool RollbackFromLastGood(std::string& err);
|
||||
Status Rollback();
|
||||
|
||||
bool UpdateNodeConfig(const std::string& node_id, const std::optional<std::string>& graph,
|
||||
const SimpleJson& new_node_cfg, std::string& err);
|
||||
Status SetNodeConfig(const std::string& node_id, const SimpleJson& new_node_cfg,
|
||||
const std::optional<std::string>& graph = std::nullopt);
|
||||
|
||||
std::vector<GraphSnapshot> ListGraphSnapshots();
|
||||
bool GetGraphSnapshot(const std::string& name, GraphSnapshot& out, std::string& err);
|
||||
Result<GraphSnapshot> GetGraph(const std::string& name);
|
||||
|
||||
// If graph is not set: auto-match when unique, otherwise return false with err describing ambiguity.
|
||||
bool GetNodeSnapshot(const std::string& node_id, const std::optional<std::string>& graph,
|
||||
NodeSnapshot& out, std::string& err);
|
||||
Result<NodeSnapshot> GetNode(const std::string& node_id,
|
||||
const std::optional<std::string>& graph = std::nullopt);
|
||||
|
||||
private:
|
||||
bool running_ = false;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "node.h"
|
||||
#include "utils/result.h"
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
@ -19,6 +20,10 @@ public:
|
||||
PluginLoader(PluginLoader&& other) noexcept;
|
||||
PluginLoader& operator=(PluginLoader&& other) noexcept;
|
||||
|
||||
// New Result-based API
|
||||
Result<NodePtr> CreateNode(const std::string& type);
|
||||
|
||||
// Legacy API (deprecated, use CreateNode instead)
|
||||
NodePtr Create(const std::string& type, std::string& err);
|
||||
|
||||
// Switch plugin directory. This will unload any cached plugins.
|
||||
|
||||
121
include/utils/result.h
Normal file
121
include/utils/result.h
Normal file
@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
// Error type for Result
|
||||
class Error {
|
||||
public:
|
||||
Error() = default;
|
||||
explicit Error(std::string message) : message_(std::move(message)) {}
|
||||
Error(std::string message, int code) : message_(std::move(message)), code_(code) {}
|
||||
|
||||
const std::string& Message() const { return message_; }
|
||||
int Code() const { return code_; }
|
||||
bool Empty() const { return message_.empty(); }
|
||||
|
||||
explicit operator bool() const { return !message_.empty(); }
|
||||
|
||||
private:
|
||||
std::string message_;
|
||||
int code_ = 0;
|
||||
};
|
||||
|
||||
// Result<T> for functions that return a value or error
|
||||
template <typename T>
|
||||
class Result {
|
||||
public:
|
||||
// NOLINTNEXTLINE: implicit conversion is intentional for ergonomic API
|
||||
Result(T value) noexcept(std::is_nothrow_move_constructible_v<T>)
|
||||
: data_(std::move(value)) {}
|
||||
// NOLINTNEXTLINE: implicit conversion is intentional for ergonomic API
|
||||
Result(Error error) noexcept : data_(std::move(error)) {}
|
||||
|
||||
bool Ok() const noexcept { return std::holds_alternative<T>(data_); }
|
||||
bool Failed() const noexcept { return !Ok(); }
|
||||
explicit operator bool() const noexcept { return Ok(); }
|
||||
|
||||
const T& Value() const& { return std::get<T>(data_); }
|
||||
T& Value() & { return std::get<T>(data_); }
|
||||
T&& Value() && { return std::get<T>(std::move(data_)); }
|
||||
|
||||
const T& ValueOr(const T& def) const& {
|
||||
return Ok() ? std::get<T>(data_) : def;
|
||||
}
|
||||
|
||||
const Error& Err() const { return std::get<Error>(data_); }
|
||||
const std::string& ErrMessage() const {
|
||||
static const std::string kEmpty;
|
||||
return Failed() ? std::get<Error>(data_).Message() : kEmpty;
|
||||
}
|
||||
|
||||
// Convenience: extract value, moving it out
|
||||
T Take() { return std::get<T>(std::move(data_)); }
|
||||
|
||||
private:
|
||||
std::variant<T, Error> data_;
|
||||
};
|
||||
|
||||
// Result<void> specialization for functions that only succeed/fail
|
||||
template <>
|
||||
class Result<void> {
|
||||
public:
|
||||
Result() noexcept : error_() {}
|
||||
// NOLINTNEXTLINE: implicit conversion is intentional for ergonomic API
|
||||
Result(Error error) noexcept : error_(std::move(error)) {}
|
||||
|
||||
static Result Ok() noexcept { return Result(); }
|
||||
static Result Fail(std::string message) { return Result(Error(std::move(message))); }
|
||||
static Result Fail(std::string message, int code) {
|
||||
return Result(Error(std::move(message), code));
|
||||
}
|
||||
|
||||
bool IsOk() const noexcept { return error_.Empty(); }
|
||||
bool Failed() const noexcept { return !IsOk(); }
|
||||
explicit operator bool() const noexcept { return IsOk(); }
|
||||
|
||||
const Error& Err() const { return error_; }
|
||||
const std::string& ErrMessage() const { return error_.Message(); }
|
||||
|
||||
private:
|
||||
Error error_;
|
||||
};
|
||||
|
||||
// Type aliases for common patterns
|
||||
using Status = Result<void>;
|
||||
|
||||
// Helper macros for early return on error
|
||||
#define RK_TRY(expr) \
|
||||
do { \
|
||||
auto _result = (expr); \
|
||||
if (_result.Failed()) { \
|
||||
return _result.Err(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define RK_TRY_ASSIGN(var, expr) \
|
||||
auto _result_##var = (expr); \
|
||||
if (_result_##var.Failed()) { \
|
||||
return _result_##var.Err(); \
|
||||
} \
|
||||
var = std::move(_result_##var).Take()
|
||||
|
||||
// Helper functions
|
||||
inline Status OkStatus() { return Status::Ok(); }
|
||||
inline Status FailStatus(std::string msg) { return Status::Fail(std::move(msg)); }
|
||||
|
||||
template <typename T>
|
||||
Result<T> MakeResult(T value) {
|
||||
return Result<T>(std::move(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result<T> MakeError(std::string message) {
|
||||
return Result<T>(Error(std::move(message)));
|
||||
}
|
||||
|
||||
} // namespace rk3588
|
||||
@ -1603,4 +1603,75 @@ bool GraphManager::GetNodeSnapshot(const std::string& node_id, const std::option
|
||||
return false;
|
||||
}
|
||||
|
||||
// New Result-based API implementations
|
||||
|
||||
Result<SimpleJson> GraphManager::LoadConfig(const std::string& path) {
|
||||
SimpleJson out;
|
||||
std::string err;
|
||||
if (!LoadConfigFile(path, out, err)) {
|
||||
return Error(err);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Status GraphManager::BuildFromConfig(const SimpleJson& root_cfg) {
|
||||
std::string err;
|
||||
if (!Build(root_cfg, err)) {
|
||||
return Status::Fail(err);
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status GraphManager::BuildFromPath(const std::string& path) {
|
||||
std::string err;
|
||||
if (!BuildFromFile(path, err)) {
|
||||
return Status::Fail(err);
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status GraphManager::Reload(const std::string& path) {
|
||||
std::string err;
|
||||
if (!ReloadFromFile(path, err)) {
|
||||
return Status::Fail(err);
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status GraphManager::Rollback() {
|
||||
std::string err;
|
||||
if (!RollbackFromLastGood(err)) {
|
||||
return Status::Fail(err);
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status GraphManager::SetNodeConfig(const std::string& node_id, const SimpleJson& new_node_cfg,
|
||||
const std::optional<std::string>& graph) {
|
||||
std::string err;
|
||||
if (!UpdateNodeConfig(node_id, graph, new_node_cfg, err)) {
|
||||
return Status::Fail(err);
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Result<GraphSnapshot> GraphManager::GetGraph(const std::string& name) {
|
||||
GraphSnapshot out;
|
||||
std::string err;
|
||||
if (!GetGraphSnapshot(name, out, err)) {
|
||||
return Error(err);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Result<NodeSnapshot> GraphManager::GetNode(const std::string& node_id,
|
||||
const std::optional<std::string>& graph) {
|
||||
NodeSnapshot out;
|
||||
std::string err;
|
||||
if (!GetNodeSnapshot(node_id, graph, out, err)) {
|
||||
return Error(err);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace rk3588
|
||||
|
||||
@ -145,13 +145,14 @@ bool PluginLoader::LoadPlugin(const std::string& type, PluginHandle& out, std::s
|
||||
return true;
|
||||
}
|
||||
|
||||
NodePtr PluginLoader::Create(const std::string& type, std::string& err) {
|
||||
Result<NodePtr> PluginLoader::CreateNode(const std::string& type) {
|
||||
auto it = cache_.find(type);
|
||||
if (it == cache_.end()) {
|
||||
PluginHandle handle;
|
||||
std::string err;
|
||||
if (!LoadPlugin(type, handle, err)) {
|
||||
LogError("[PluginLoader] Failed to load plugin '" + type + "': " + err);
|
||||
return NodePtr(nullptr, NodeDeleter{});
|
||||
return Error("Failed to load plugin '" + type + "': " + err);
|
||||
}
|
||||
it = cache_.emplace(type, std::move(handle)).first;
|
||||
}
|
||||
@ -159,10 +160,18 @@ NodePtr PluginLoader::Create(const std::string& type, std::string& err) {
|
||||
PluginHandle& handle = it->second;
|
||||
NodePtr node(handle.create(), NodeDeleter{handle.destroy});
|
||||
if (!node) {
|
||||
err = "CreateNode returned null for type " + type;
|
||||
return NodePtr(nullptr, NodeDeleter{handle.destroy});
|
||||
return Error("CreateNode returned null for type " + type);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
NodePtr PluginLoader::Create(const std::string& type, std::string& err) {
|
||||
auto result = CreateNode(type);
|
||||
if (result.Failed()) {
|
||||
err = result.ErrMessage();
|
||||
return NodePtr(nullptr, NodeDeleter{});
|
||||
}
|
||||
return std::move(result).Take();
|
||||
}
|
||||
|
||||
} // namespace rk3588
|
||||
|
||||
@ -1,13 +1,49 @@
|
||||
add_executable(rk3588_tests
|
||||
include(FetchContent)
|
||||
|
||||
# Fetch Google Test
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG v1.14.0
|
||||
)
|
||||
# Prevent overriding the parent project's compiler/linker settings on Windows
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
# Legacy simple tests (keep for backward compatibility)
|
||||
add_executable(rk3588_tests_legacy
|
||||
test_main.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/utils/config_expand.cpp
|
||||
)
|
||||
|
||||
target_include_directories(rk3588_tests PRIVATE
|
||||
target_include_directories(rk3588_tests_legacy PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/third_party
|
||||
)
|
||||
|
||||
target_link_libraries(rk3588_tests PRIVATE project_options Threads::Threads)
|
||||
target_link_libraries(rk3588_tests_legacy PRIVATE project_options Threads::Threads)
|
||||
|
||||
add_test(NAME rk3588_tests COMMAND rk3588_tests)
|
||||
add_test(NAME rk3588_tests_legacy COMMAND rk3588_tests_legacy)
|
||||
|
||||
# New Google Test based tests
|
||||
add_executable(rk3588_gtests
|
||||
test_result.cpp
|
||||
test_spsc_queue.cpp
|
||||
test_simple_json.cpp
|
||||
test_config_expand.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/utils/config_expand.cpp
|
||||
)
|
||||
|
||||
target_include_directories(rk3588_gtests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/third_party
|
||||
)
|
||||
|
||||
target_link_libraries(rk3588_gtests PRIVATE
|
||||
project_options
|
||||
Threads::Threads
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(rk3588_gtests)
|
||||
|
||||
387
tests/test_config_expand.cpp
Normal file
387
tests/test_config_expand.cpp
Normal file
@ -0,0 +1,387 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utils/config_expand.h"
|
||||
#include "utils/config_schema.h"
|
||||
#include "utils/simple_json.h"
|
||||
|
||||
namespace rk3588 {
|
||||
namespace {
|
||||
|
||||
TEST(ConfigExpandTest, ExpandEmptyConfig) {
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("{}", root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
EXPECT_TRUE(expanded.IsObject());
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, PassthroughGraphs) {
|
||||
const char* json = R"({
|
||||
"graphs": [
|
||||
{
|
||||
"name": "test",
|
||||
"nodes": [{"id": "n1", "type": "input_file", "role": "source"}],
|
||||
"edges": []
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
|
||||
const SimpleJson* graphs = expanded.Find("graphs");
|
||||
ASSERT_NE(graphs, nullptr);
|
||||
EXPECT_TRUE(graphs->IsArray());
|
||||
EXPECT_EQ(graphs->AsArray().size(), 1u);
|
||||
EXPECT_EQ(graphs->AsArray()[0].ValueOr<std::string>("name", ""), "test");
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, ExpandTemplate) {
|
||||
const char* json = R"({
|
||||
"templates": {
|
||||
"simple": {
|
||||
"nodes": [
|
||||
{"id": "src", "type": "input_file", "role": "source", "path": "${path}"}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
"name": "cam1",
|
||||
"template": "simple",
|
||||
"params": {"path": "/video/test.mp4"}
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
|
||||
const SimpleJson* graphs = expanded.Find("graphs");
|
||||
ASSERT_NE(graphs, nullptr);
|
||||
EXPECT_EQ(graphs->AsArray().size(), 1u);
|
||||
|
||||
const SimpleJson& g = graphs->AsArray()[0];
|
||||
EXPECT_EQ(g.ValueOr<std::string>("name", ""), "cam1");
|
||||
|
||||
const SimpleJson* nodes = g.Find("nodes");
|
||||
ASSERT_NE(nodes, nullptr);
|
||||
EXPECT_EQ(nodes->AsArray().size(), 1u);
|
||||
|
||||
// Node ID should be prefixed with instance name
|
||||
EXPECT_EQ(nodes->AsArray()[0].ValueOr<std::string>("id", ""), "cam1_src");
|
||||
// Placeholder should be replaced
|
||||
EXPECT_EQ(nodes->AsArray()[0].ValueOr<std::string>("path", ""), "/video/test.mp4");
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, ExpandMultipleInstances) {
|
||||
const char* json = R"({
|
||||
"templates": {
|
||||
"t": {
|
||||
"nodes": [{"id": "n", "type": "test", "role": "source"}],
|
||||
"edges": []
|
||||
}
|
||||
},
|
||||
"instances": [
|
||||
{"name": "i1", "template": "t", "params": {}},
|
||||
{"name": "i2", "template": "t", "params": {}}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
|
||||
const SimpleJson* graphs = expanded.Find("graphs");
|
||||
ASSERT_NE(graphs, nullptr);
|
||||
EXPECT_EQ(graphs->AsArray().size(), 2u);
|
||||
EXPECT_EQ(graphs->AsArray()[0].ValueOr<std::string>("name", ""), "i1");
|
||||
EXPECT_EQ(graphs->AsArray()[1].ValueOr<std::string>("name", ""), "i2");
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, ExpandWithOverride) {
|
||||
const char* json = R"({
|
||||
"templates": {
|
||||
"t": {
|
||||
"nodes": [
|
||||
{"id": "n1", "type": "test", "role": "source", "enable": true},
|
||||
{"id": "n2", "type": "filter", "role": "filter"}
|
||||
],
|
||||
"edges": [["n1", "n2"]]
|
||||
}
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
"name": "inst",
|
||||
"template": "t",
|
||||
"params": {},
|
||||
"override": {
|
||||
"nodes": {
|
||||
"n1": {"enable": false}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
|
||||
const SimpleJson* graphs = expanded.Find("graphs");
|
||||
ASSERT_NE(graphs, nullptr);
|
||||
|
||||
const SimpleJson* nodes = graphs->AsArray()[0].Find("nodes");
|
||||
ASSERT_NE(nodes, nullptr);
|
||||
|
||||
// Find the overridden node
|
||||
bool found = false;
|
||||
for (const auto& n : nodes->AsArray()) {
|
||||
if (n.ValueOr<std::string>("id", "") == "inst_n1") {
|
||||
found = true;
|
||||
EXPECT_FALSE(n.ValueOr<bool>("enable", true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, EdgeIdPrefixing) {
|
||||
const char* json = R"({
|
||||
"templates": {
|
||||
"t": {
|
||||
"nodes": [
|
||||
{"id": "src", "type": "source", "role": "source"},
|
||||
{"id": "sink", "type": "sink", "role": "sink"}
|
||||
],
|
||||
"edges": [["src", "sink"]]
|
||||
}
|
||||
},
|
||||
"instances": [
|
||||
{"name": "g1", "template": "t", "params": {}}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
|
||||
const SimpleJson* graphs = expanded.Find("graphs");
|
||||
ASSERT_NE(graphs, nullptr);
|
||||
|
||||
const SimpleJson* edges = graphs->AsArray()[0].Find("edges");
|
||||
ASSERT_NE(edges, nullptr);
|
||||
EXPECT_EQ(edges->AsArray().size(), 1u);
|
||||
|
||||
// Edge endpoints should be prefixed
|
||||
const SimpleJson& edge = edges->AsArray()[0];
|
||||
EXPECT_TRUE(edge.IsArray());
|
||||
EXPECT_EQ(edge.AsArray()[0].AsString(), "g1_src");
|
||||
EXPECT_EQ(edge.AsArray()[1].AsString(), "g1_sink");
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, QueueConfigPreserved) {
|
||||
const char* json = R"({
|
||||
"queue": {"size": 16, "strategy": "drop_newest"},
|
||||
"graphs": [
|
||||
{
|
||||
"name": "test",
|
||||
"nodes": [],
|
||||
"edges": []
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
|
||||
const SimpleJson* queue = expanded.Find("queue");
|
||||
ASSERT_NE(queue, nullptr);
|
||||
EXPECT_EQ(queue->ValueOr<int>("size", 0), 16);
|
||||
EXPECT_EQ(queue->ValueOr<std::string>("strategy", ""), "drop_newest");
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, GlobalConfigPreserved) {
|
||||
const char* json = R"({
|
||||
"global": {"metrics_port": 8080, "log_level": "debug"},
|
||||
"graphs": []
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
|
||||
const SimpleJson* global = expanded.Find("global");
|
||||
ASSERT_NE(global, nullptr);
|
||||
EXPECT_EQ(global->ValueOr<int>("metrics_port", 0), 8080);
|
||||
EXPECT_EQ(global->ValueOr<std::string>("log_level", ""), "debug");
|
||||
}
|
||||
|
||||
TEST(ConfigExpandTest, MissingTemplateError) {
|
||||
const char* json = R"({
|
||||
"templates": {},
|
||||
"instances": [
|
||||
{"name": "inst", "template": "nonexistent", "params": {}}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_FALSE(ExpandRootConfig(root, expanded, err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
// Schema validation tests
|
||||
|
||||
TEST(ConfigSchemaTest, ValidGraphConfig) {
|
||||
const char* json = R"({
|
||||
"graphs": [
|
||||
{
|
||||
"name": "valid",
|
||||
"nodes": [
|
||||
{"id": "n1", "type": "input_file", "role": "source"},
|
||||
{"id": "n2", "type": "output", "role": "sink"}
|
||||
],
|
||||
"edges": [["n1", "n2"]]
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
EXPECT_TRUE(ValidateExpandedRootConfig(expanded, err)) << err;
|
||||
}
|
||||
|
||||
TEST(ConfigSchemaTest, MissingGraphsArray) {
|
||||
const char* json = R"({})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
// Should pass since empty graphs is allowed after expand
|
||||
}
|
||||
|
||||
TEST(ConfigSchemaTest, NodeMissingId) {
|
||||
const char* json = R"({
|
||||
"graphs": [
|
||||
{
|
||||
"name": "test",
|
||||
"nodes": [{"type": "input", "role": "source"}],
|
||||
"edges": []
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
EXPECT_FALSE(ValidateExpandedRootConfig(expanded, err));
|
||||
}
|
||||
|
||||
TEST(ConfigSchemaTest, NodeMissingType) {
|
||||
const char* json = R"({
|
||||
"graphs": [
|
||||
{
|
||||
"name": "test",
|
||||
"nodes": [{"id": "n1", "role": "source"}],
|
||||
"edges": []
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
EXPECT_FALSE(ValidateExpandedRootConfig(expanded, err));
|
||||
}
|
||||
|
||||
TEST(ConfigSchemaTest, DuplicateNodeId) {
|
||||
const char* json = R"({
|
||||
"graphs": [
|
||||
{
|
||||
"name": "test",
|
||||
"nodes": [
|
||||
{"id": "same", "type": "t1", "role": "source"},
|
||||
{"id": "same", "type": "t2", "role": "sink"}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
EXPECT_FALSE(ValidateExpandedRootConfig(expanded, err));
|
||||
}
|
||||
|
||||
TEST(ConfigSchemaTest, EdgeReferencesUnknownNode) {
|
||||
const char* json = R"({
|
||||
"graphs": [
|
||||
{
|
||||
"name": "test",
|
||||
"nodes": [{"id": "n1", "type": "t", "role": "source"}],
|
||||
"edges": [["n1", "unknown"]]
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
SimpleJson root;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, root, err));
|
||||
|
||||
SimpleJson expanded;
|
||||
EXPECT_TRUE(ExpandRootConfig(root, expanded, err));
|
||||
EXPECT_FALSE(ValidateExpandedRootConfig(expanded, err));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace rk3588
|
||||
137
tests/test_result.cpp
Normal file
137
tests/test_result.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "utils/result.h"
|
||||
|
||||
namespace rk3588 {
|
||||
namespace {
|
||||
|
||||
TEST(ResultTest, VoidResultOk) {
|
||||
Status s = Status::Ok();
|
||||
EXPECT_TRUE(s.IsOk());
|
||||
EXPECT_FALSE(s.Failed());
|
||||
EXPECT_TRUE(static_cast<bool>(s));
|
||||
EXPECT_TRUE(s.ErrMessage().empty());
|
||||
}
|
||||
|
||||
TEST(ResultTest, VoidResultFail) {
|
||||
Status s = Status::Fail("something went wrong");
|
||||
EXPECT_FALSE(s.IsOk());
|
||||
EXPECT_TRUE(s.Failed());
|
||||
EXPECT_FALSE(static_cast<bool>(s));
|
||||
EXPECT_EQ(s.ErrMessage(), "something went wrong");
|
||||
}
|
||||
|
||||
TEST(ResultTest, VoidResultFailWithCode) {
|
||||
Status s = Status::Fail("error", 42);
|
||||
EXPECT_FALSE(s.IsOk());
|
||||
EXPECT_EQ(s.Err().Code(), 42);
|
||||
EXPECT_EQ(s.ErrMessage(), "error");
|
||||
}
|
||||
|
||||
TEST(ResultTest, ValueResultOk) {
|
||||
Result<int> r = 42;
|
||||
EXPECT_TRUE(r.Ok());
|
||||
EXPECT_FALSE(r.Failed());
|
||||
EXPECT_TRUE(static_cast<bool>(r));
|
||||
EXPECT_EQ(r.Value(), 42);
|
||||
}
|
||||
|
||||
TEST(ResultTest, ValueResultFail) {
|
||||
Result<int> r = Error("failed to compute");
|
||||
EXPECT_FALSE(r.Ok());
|
||||
EXPECT_TRUE(r.Failed());
|
||||
EXPECT_FALSE(static_cast<bool>(r));
|
||||
EXPECT_EQ(r.ErrMessage(), "failed to compute");
|
||||
}
|
||||
|
||||
TEST(ResultTest, ValueResultValueOr) {
|
||||
Result<int> ok = 100;
|
||||
Result<int> fail = Error("oops");
|
||||
|
||||
EXPECT_EQ(ok.ValueOr(0), 100);
|
||||
EXPECT_EQ(fail.ValueOr(0), 0);
|
||||
}
|
||||
|
||||
TEST(ResultTest, ValueResultTake) {
|
||||
Result<std::string> r = std::string("hello");
|
||||
EXPECT_TRUE(r.Ok());
|
||||
std::string s = std::move(r).Take();
|
||||
EXPECT_EQ(s, "hello");
|
||||
}
|
||||
|
||||
TEST(ResultTest, StringResult) {
|
||||
Result<std::string> ok = std::string("success");
|
||||
Result<std::string> fail = Error("failure");
|
||||
|
||||
EXPECT_TRUE(ok.Ok());
|
||||
EXPECT_EQ(ok.Value(), "success");
|
||||
|
||||
EXPECT_TRUE(fail.Failed());
|
||||
EXPECT_EQ(fail.ErrMessage(), "failure");
|
||||
}
|
||||
|
||||
TEST(ResultTest, MakeResultHelper) {
|
||||
auto r = MakeResult<int>(123);
|
||||
EXPECT_TRUE(r.Ok());
|
||||
EXPECT_EQ(r.Value(), 123);
|
||||
}
|
||||
|
||||
TEST(ResultTest, MakeErrorHelper) {
|
||||
auto r = MakeError<int>("test error");
|
||||
EXPECT_TRUE(r.Failed());
|
||||
EXPECT_EQ(r.ErrMessage(), "test error");
|
||||
}
|
||||
|
||||
TEST(ErrorTest, Basic) {
|
||||
Error e1;
|
||||
EXPECT_TRUE(e1.Empty());
|
||||
EXPECT_FALSE(static_cast<bool>(e1));
|
||||
|
||||
Error e2("message", 100);
|
||||
EXPECT_FALSE(e2.Empty());
|
||||
EXPECT_TRUE(static_cast<bool>(e2));
|
||||
EXPECT_EQ(e2.Message(), "message");
|
||||
EXPECT_EQ(e2.Code(), 100);
|
||||
}
|
||||
|
||||
TEST(ResultTest, OkStatusHelper) {
|
||||
Status s = OkStatus();
|
||||
EXPECT_TRUE(s.IsOk());
|
||||
}
|
||||
|
||||
TEST(ResultTest, FailStatusHelper) {
|
||||
Status s = FailStatus("test");
|
||||
EXPECT_TRUE(s.Failed());
|
||||
EXPECT_EQ(s.ErrMessage(), "test");
|
||||
}
|
||||
|
||||
// Test with complex types
|
||||
struct ComplexType {
|
||||
int x;
|
||||
std::string s;
|
||||
bool operator==(const ComplexType& o) const { return x == o.x && s == o.s; }
|
||||
};
|
||||
|
||||
TEST(ResultTest, ComplexType) {
|
||||
Result<ComplexType> ok = ComplexType{42, "test"};
|
||||
EXPECT_TRUE(ok.Ok());
|
||||
EXPECT_EQ(ok.Value().x, 42);
|
||||
EXPECT_EQ(ok.Value().s, "test");
|
||||
|
||||
Result<ComplexType> fail = Error("complex error");
|
||||
EXPECT_TRUE(fail.Failed());
|
||||
}
|
||||
|
||||
// Test move semantics
|
||||
TEST(ResultTest, MoveSemantics) {
|
||||
Result<std::unique_ptr<int>> r = std::make_unique<int>(42);
|
||||
EXPECT_TRUE(r.Ok());
|
||||
auto ptr = std::move(r).Take();
|
||||
EXPECT_EQ(*ptr, 42);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace rk3588
|
||||
313
tests/test_simple_json.cpp
Normal file
313
tests/test_simple_json.cpp
Normal file
@ -0,0 +1,313 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utils/simple_json.h"
|
||||
|
||||
namespace rk3588 {
|
||||
namespace {
|
||||
|
||||
TEST(SimpleJsonTest, ParseNull) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("null", v, err));
|
||||
EXPECT_TRUE(v.IsNull());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseBoolTrue) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("true", v, err));
|
||||
EXPECT_TRUE(v.IsBool());
|
||||
EXPECT_TRUE(v.AsBool());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseBoolFalse) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("false", v, err));
|
||||
EXPECT_TRUE(v.IsBool());
|
||||
EXPECT_FALSE(v.AsBool());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseInteger) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("42", v, err));
|
||||
EXPECT_TRUE(v.IsNumber());
|
||||
EXPECT_EQ(v.AsNumber(), 42.0);
|
||||
EXPECT_EQ(v.AsInt(), 42);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseNegativeNumber) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("-123", v, err));
|
||||
EXPECT_TRUE(v.IsNumber());
|
||||
EXPECT_EQ(v.AsInt(), -123);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseFloat) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("3.14159", v, err));
|
||||
EXPECT_TRUE(v.IsNumber());
|
||||
EXPECT_NEAR(v.AsNumber(), 3.14159, 0.00001);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseScientific) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("1.5e10", v, err));
|
||||
EXPECT_TRUE(v.IsNumber());
|
||||
EXPECT_NEAR(v.AsNumber(), 1.5e10, 1e6);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseString) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("\"hello world\"", v, err));
|
||||
EXPECT_TRUE(v.IsString());
|
||||
EXPECT_EQ(v.AsString(), "hello world");
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseStringEscapes) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(R"("line1\nline2\ttab\\slash\"quote")", v, err));
|
||||
EXPECT_TRUE(v.IsString());
|
||||
EXPECT_EQ(v.AsString(), "line1\nline2\ttab\\slash\"quote");
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseEmptyArray) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("[]", v, err));
|
||||
EXPECT_TRUE(v.IsArray());
|
||||
EXPECT_TRUE(v.AsArray().empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseArray) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("[1, 2, 3]", v, err));
|
||||
EXPECT_TRUE(v.IsArray());
|
||||
EXPECT_EQ(v.AsArray().size(), 3u);
|
||||
EXPECT_EQ(v.AsArray()[0].AsInt(), 1);
|
||||
EXPECT_EQ(v.AsArray()[1].AsInt(), 2);
|
||||
EXPECT_EQ(v.AsArray()[2].AsInt(), 3);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseMixedArray) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("[1, \"two\", true, null]", v, err));
|
||||
EXPECT_TRUE(v.IsArray());
|
||||
EXPECT_EQ(v.AsArray().size(), 4u);
|
||||
EXPECT_TRUE(v.AsArray()[0].IsNumber());
|
||||
EXPECT_TRUE(v.AsArray()[1].IsString());
|
||||
EXPECT_TRUE(v.AsArray()[2].IsBool());
|
||||
EXPECT_TRUE(v.AsArray()[3].IsNull());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseEmptyObject) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("{}", v, err));
|
||||
EXPECT_TRUE(v.IsObject());
|
||||
EXPECT_TRUE(v.AsObject().empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseObject) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(R"({"name": "test", "value": 42})", v, err));
|
||||
EXPECT_TRUE(v.IsObject());
|
||||
EXPECT_EQ(v.AsObject().size(), 2u);
|
||||
EXPECT_EQ(v.ValueOr<std::string>("name", ""), "test");
|
||||
EXPECT_EQ(v.ValueOr<int>("value", 0), 42);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseNestedObject) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(R"({"outer": {"inner": "value"}})", v, err));
|
||||
EXPECT_TRUE(v.IsObject());
|
||||
const SimpleJson* outer = v.Find("outer");
|
||||
ASSERT_NE(outer, nullptr);
|
||||
EXPECT_TRUE(outer->IsObject());
|
||||
EXPECT_EQ(outer->ValueOr<std::string>("inner", ""), "value");
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseNestedArray) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("[[1, 2], [3, 4]]", v, err));
|
||||
EXPECT_TRUE(v.IsArray());
|
||||
EXPECT_EQ(v.AsArray().size(), 2u);
|
||||
EXPECT_EQ(v.AsArray()[0].AsArray().size(), 2u);
|
||||
EXPECT_EQ(v.AsArray()[0].AsArray()[0].AsInt(), 1);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, FindNonExistent) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(R"({"a": 1})", v, err));
|
||||
EXPECT_EQ(v.Find("b"), nullptr);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ValueOrDefaults) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(R"({"a": 1})", v, err));
|
||||
EXPECT_EQ(v.ValueOr<int>("a", 0), 1);
|
||||
EXPECT_EQ(v.ValueOr<int>("b", 99), 99);
|
||||
EXPECT_EQ(v.ValueOr<std::string>("c", "default"), "default");
|
||||
EXPECT_EQ(v.ValueOr<bool>("d", true), true);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, StringToBoolCoercion) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(R"({"a": "true", "b": "false", "c": "yes", "d": "no"})", v, err));
|
||||
EXPECT_TRUE(v.ValueOr<bool>("a", false));
|
||||
EXPECT_FALSE(v.ValueOr<bool>("b", true));
|
||||
EXPECT_TRUE(v.ValueOr<bool>("c", false));
|
||||
EXPECT_FALSE(v.ValueOr<bool>("d", true));
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, StringToNumberCoercion) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(R"({"a": "42", "b": "3.14", "c": "not a number"})", v, err));
|
||||
EXPECT_EQ(v.ValueOr<int>("a", 0), 42);
|
||||
EXPECT_NEAR(v.ValueOr<double>("b", 0.0), 3.14, 0.001);
|
||||
EXPECT_EQ(v.ValueOr<double>("c", -1.0), -1.0); // returns default
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseError_UnterminatedString) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_FALSE(ParseSimpleJson("\"unterminated", v, err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseError_UnterminatedArray) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_FALSE(ParseSimpleJson("[1, 2, 3", v, err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseError_UnterminatedObject) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_FALSE(ParseSimpleJson("{\"a\": 1", v, err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseError_TrailingContent) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_FALSE(ParseSimpleJson("{\"a\": 1} extra", v, err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseError_InvalidValue) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_FALSE(ParseSimpleJson("undefined", v, err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseError_MissingColon) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_FALSE(ParseSimpleJson("{\"a\" 1}", v, err));
|
||||
EXPECT_FALSE(err.empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ParseWhitespace) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(" \n\t { \"a\" : 1 } \n ", v, err));
|
||||
EXPECT_TRUE(v.IsObject());
|
||||
EXPECT_EQ(v.ValueOr<int>("a", 0), 1);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ComplexDocument) {
|
||||
const char* json = R"({
|
||||
"name": "test_config",
|
||||
"version": 1.5,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"timeout": 30,
|
||||
"retries": 3
|
||||
},
|
||||
"items": [
|
||||
{"id": 1, "name": "first"},
|
||||
{"id": 2, "name": "second"}
|
||||
],
|
||||
"tags": ["a", "b", "c"]
|
||||
})";
|
||||
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson(json, v, err));
|
||||
EXPECT_TRUE(v.IsObject());
|
||||
|
||||
EXPECT_EQ(v.ValueOr<std::string>("name", ""), "test_config");
|
||||
EXPECT_NEAR(v.ValueOr<double>("version", 0.0), 1.5, 0.01);
|
||||
EXPECT_TRUE(v.ValueOr<bool>("enabled", false));
|
||||
|
||||
const SimpleJson* settings = v.Find("settings");
|
||||
ASSERT_NE(settings, nullptr);
|
||||
EXPECT_EQ(settings->ValueOr<int>("timeout", 0), 30);
|
||||
EXPECT_EQ(settings->ValueOr<int>("retries", 0), 3);
|
||||
|
||||
const SimpleJson* items = v.Find("items");
|
||||
ASSERT_NE(items, nullptr);
|
||||
EXPECT_EQ(items->AsArray().size(), 2u);
|
||||
EXPECT_EQ(items->AsArray()[0].ValueOr<int>("id", 0), 1);
|
||||
EXPECT_EQ(items->AsArray()[1].ValueOr<std::string>("name", ""), "second");
|
||||
|
||||
const SimpleJson* tags = v.Find("tags");
|
||||
ASSERT_NE(tags, nullptr);
|
||||
EXPECT_EQ(tags->AsArray().size(), 3u);
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, AsArrayOnNonArray) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("42", v, err));
|
||||
// Should return empty array reference
|
||||
EXPECT_TRUE(v.AsArray().empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, AsObjectOnNonObject) {
|
||||
SimpleJson v;
|
||||
std::string err;
|
||||
EXPECT_TRUE(ParseSimpleJson("[1,2,3]", v, err));
|
||||
// Should return empty object reference
|
||||
EXPECT_TRUE(v.AsObject().empty());
|
||||
}
|
||||
|
||||
TEST(SimpleJsonTest, ConstructFromValues) {
|
||||
SimpleJson null_val(nullptr);
|
||||
EXPECT_TRUE(null_val.IsNull());
|
||||
|
||||
SimpleJson bool_val(true);
|
||||
EXPECT_TRUE(bool_val.IsBool());
|
||||
EXPECT_TRUE(bool_val.AsBool());
|
||||
|
||||
SimpleJson num_val(42.0);
|
||||
EXPECT_TRUE(num_val.IsNumber());
|
||||
EXPECT_EQ(num_val.AsNumber(), 42.0);
|
||||
|
||||
SimpleJson str_val(std::string("hello"));
|
||||
EXPECT_TRUE(str_val.IsString());
|
||||
EXPECT_EQ(str_val.AsString(), "hello");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace rk3588
|
||||
283
tests/test_spsc_queue.cpp
Normal file
283
tests/test_spsc_queue.cpp
Normal file
@ -0,0 +1,283 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/spsc_queue.h"
|
||||
|
||||
namespace rk3588 {
|
||||
namespace {
|
||||
|
||||
TEST(SpscQueueTest, BasicPushPop) {
|
||||
SpscQueue<int> q(4, QueueDropStrategy::DropOldest);
|
||||
EXPECT_EQ(q.Capacity(), 4u);
|
||||
EXPECT_EQ(q.Size(), 0u);
|
||||
|
||||
EXPECT_TRUE(q.Push(1));
|
||||
EXPECT_TRUE(q.Push(2));
|
||||
EXPECT_TRUE(q.Push(3));
|
||||
EXPECT_EQ(q.Size(), 3u);
|
||||
|
||||
int v = 0;
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 1);
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 2);
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 3);
|
||||
EXPECT_EQ(q.Size(), 0u);
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, TryPopEmpty) {
|
||||
SpscQueue<int> q(4, QueueDropStrategy::DropOldest);
|
||||
int v = -1;
|
||||
EXPECT_FALSE(q.TryPop(v));
|
||||
EXPECT_EQ(v, -1); // unchanged
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, DropOldestStrategy) {
|
||||
SpscQueue<int> q(2, QueueDropStrategy::DropOldest);
|
||||
|
||||
EXPECT_TRUE(q.Push(1));
|
||||
EXPECT_TRUE(q.Push(2));
|
||||
EXPECT_EQ(q.Size(), 2u);
|
||||
EXPECT_EQ(q.DroppedCount(), 0u);
|
||||
|
||||
// Queue full, should drop oldest (1)
|
||||
EXPECT_TRUE(q.Push(3));
|
||||
EXPECT_EQ(q.DroppedCount(), 1u);
|
||||
|
||||
int v = 0;
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 2);
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 3);
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, DropNewestStrategy) {
|
||||
SpscQueue<int> q(2, QueueDropStrategy::DropNewest);
|
||||
|
||||
EXPECT_TRUE(q.Push(1));
|
||||
EXPECT_TRUE(q.Push(2));
|
||||
EXPECT_EQ(q.DroppedCount(), 0u);
|
||||
|
||||
// Queue full, should drop newest (the one being pushed)
|
||||
EXPECT_TRUE(q.Push(3));
|
||||
EXPECT_EQ(q.DroppedCount(), 1u);
|
||||
|
||||
int v = 0;
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 1);
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 2);
|
||||
EXPECT_FALSE(q.TryPop(v)); // 3 was dropped
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, BlockingStrategy) {
|
||||
SpscQueue<int> q(2, QueueDropStrategy::Block);
|
||||
|
||||
EXPECT_TRUE(q.Push(1));
|
||||
EXPECT_TRUE(q.Push(2));
|
||||
|
||||
// Start a thread that will pop after a delay
|
||||
std::thread popper([&q]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
int v;
|
||||
q.TryPop(v);
|
||||
});
|
||||
|
||||
// This should block until popper makes space
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
EXPECT_TRUE(q.Push(3));
|
||||
auto elapsed = std::chrono::steady_clock::now() - start;
|
||||
EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count(), 40);
|
||||
|
||||
popper.join();
|
||||
|
||||
int v;
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 2);
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 3);
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, PopWithTimeout) {
|
||||
SpscQueue<int> q(4, QueueDropStrategy::DropOldest);
|
||||
|
||||
// Pop on empty queue should timeout
|
||||
int v = -1;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
bool got = q.Pop(v, std::chrono::milliseconds(50));
|
||||
auto elapsed = std::chrono::steady_clock::now() - start;
|
||||
|
||||
EXPECT_FALSE(got);
|
||||
EXPECT_GE(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count(), 40);
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, PopWithTimeoutSuccess) {
|
||||
SpscQueue<int> q(4, QueueDropStrategy::DropOldest);
|
||||
|
||||
std::thread pusher([&q]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
q.Push(42);
|
||||
});
|
||||
|
||||
int v = -1;
|
||||
bool got = q.Pop(v, std::chrono::milliseconds(200));
|
||||
EXPECT_TRUE(got);
|
||||
EXPECT_EQ(v, 42);
|
||||
|
||||
pusher.join();
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, Stop) {
|
||||
SpscQueue<int> q(4, QueueDropStrategy::Block);
|
||||
q.Push(1);
|
||||
|
||||
EXPECT_FALSE(q.IsStopped());
|
||||
q.Stop();
|
||||
EXPECT_TRUE(q.IsStopped());
|
||||
|
||||
// Push should fail after stop
|
||||
EXPECT_FALSE(q.Push(2));
|
||||
|
||||
// Pop should still work for existing items
|
||||
int v = 0;
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 1);
|
||||
|
||||
// After draining, pop should fail
|
||||
EXPECT_FALSE(q.TryPop(v));
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, StopUnblocksWaiters) {
|
||||
SpscQueue<int> q(4, QueueDropStrategy::Block);
|
||||
|
||||
std::atomic<bool> popped{false};
|
||||
std::thread waiter([&q, &popped]() {
|
||||
int v;
|
||||
q.Pop(v); // Will block
|
||||
popped = true;
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
EXPECT_FALSE(popped.load());
|
||||
|
||||
q.Stop();
|
||||
|
||||
waiter.join();
|
||||
EXPECT_TRUE(popped.load());
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, Stats) {
|
||||
SpscQueue<int> q(2, QueueDropStrategy::DropOldest);
|
||||
|
||||
q.Push(1);
|
||||
q.Push(2);
|
||||
q.Push(3); // drops 1
|
||||
|
||||
int v;
|
||||
q.TryPop(v);
|
||||
|
||||
auto stats = q.GetStats();
|
||||
EXPECT_EQ(stats.capacity, 2u);
|
||||
EXPECT_EQ(stats.size, 1u);
|
||||
EXPECT_EQ(stats.pushed, 3u);
|
||||
EXPECT_EQ(stats.popped, 1u);
|
||||
EXPECT_EQ(stats.dropped, 1u);
|
||||
EXPECT_FALSE(stats.stopped);
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, TryPopBatch) {
|
||||
SpscQueue<int> q(10, QueueDropStrategy::DropOldest);
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
q.Push(i);
|
||||
}
|
||||
|
||||
std::vector<int> batch;
|
||||
bool got = q.TryPopBatch(batch, 3);
|
||||
EXPECT_TRUE(got);
|
||||
EXPECT_EQ(batch.size(), 3u);
|
||||
EXPECT_EQ(batch[0], 0);
|
||||
EXPECT_EQ(batch[1], 1);
|
||||
EXPECT_EQ(batch[2], 2);
|
||||
|
||||
got = q.TryPopBatch(batch, 10); // request more than available
|
||||
EXPECT_TRUE(got);
|
||||
EXPECT_EQ(batch.size(), 2u);
|
||||
EXPECT_EQ(batch[0], 3);
|
||||
EXPECT_EQ(batch[1], 4);
|
||||
|
||||
got = q.TryPopBatch(batch, 5); // empty queue
|
||||
EXPECT_FALSE(got);
|
||||
EXPECT_TRUE(batch.empty());
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, OnDataAvailableCallback) {
|
||||
SpscQueue<int> q(4, QueueDropStrategy::DropOldest);
|
||||
|
||||
std::atomic<int> callback_count{0};
|
||||
q.SetOnDataAvailable([&callback_count]() { callback_count.fetch_add(1); });
|
||||
|
||||
q.Push(1); // empty -> non-empty, should trigger
|
||||
EXPECT_EQ(callback_count.load(), 1);
|
||||
|
||||
q.Push(2); // not empty -> still not empty, no trigger
|
||||
EXPECT_EQ(callback_count.load(), 1);
|
||||
|
||||
int v;
|
||||
q.TryPop(v);
|
||||
q.TryPop(v); // now empty
|
||||
|
||||
q.Push(3); // empty -> non-empty, should trigger
|
||||
EXPECT_EQ(callback_count.load(), 2);
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, ConcurrentPushPop) {
|
||||
SpscQueue<int> q(100, QueueDropStrategy::DropOldest);
|
||||
constexpr int kNumItems = 10000;
|
||||
|
||||
std::atomic<int> sum{0};
|
||||
|
||||
std::thread producer([&q]() {
|
||||
for (int i = 1; i <= kNumItems; ++i) {
|
||||
q.Push(i);
|
||||
}
|
||||
});
|
||||
|
||||
std::thread consumer([&q, &sum]() {
|
||||
int received = 0;
|
||||
while (received < kNumItems) {
|
||||
int v;
|
||||
if (q.TryPop(v)) {
|
||||
sum.fetch_add(v);
|
||||
++received;
|
||||
} else {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
producer.join();
|
||||
consumer.join();
|
||||
|
||||
// Sum of 1 to kNumItems
|
||||
int expected = kNumItems * (kNumItems + 1) / 2;
|
||||
EXPECT_EQ(sum.load(), expected);
|
||||
}
|
||||
|
||||
TEST(SpscQueueTest, ZeroCapacityBecomesOne) {
|
||||
SpscQueue<int> q(0, QueueDropStrategy::DropOldest);
|
||||
EXPECT_EQ(q.Capacity(), 1u);
|
||||
|
||||
EXPECT_TRUE(q.Push(42));
|
||||
int v;
|
||||
EXPECT_TRUE(q.TryPop(v));
|
||||
EXPECT_EQ(v, 42);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace rk3588
|
||||
Loading…
Reference in New Issue
Block a user