规范优化4

This commit is contained in:
sladro 2026-01-13 08:28:55 +08:00
parent f5e5c2da1f
commit 1e5b964a7c
10 changed files with 1383 additions and 8 deletions

View File

@ -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;

View File

@ -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
View 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

View File

@ -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

View File

@ -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

View File

@ -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)

View 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
View 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
View 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
View 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