-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_run_context_store.cpp
More file actions
170 lines (143 loc) · 5.74 KB
/
test_run_context_store.cpp
File metadata and controls
170 lines (143 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Issue #27 — RunContext::store plumbing.
//
// The engine populates ctx.store from GraphEngine::set_store(...) at
// the top of every run, so node bodies can reach the Store through
// in.ctx.store without the factory-closure capture workaround.
//
// Validates: (a) ctx.store is the same shared_ptr the engine was
// configured with, (b) reads inside a running node see whatever the
// caller put into the Store before run(), (c) ctx.store is nullptr
// when no Store is configured (back-compat).
#include <gtest/gtest.h>
#include <neograph/neograph.h>
#include <neograph/graph/store.h>
#include <asio/awaitable.hpp>
#include <atomic>
#include <memory>
using namespace neograph;
using namespace neograph::graph;
namespace {
// Records what it saw on ctx.store during execute. Test asserts on
// the recorded values after run() returns.
struct StoreProbe {
std::atomic<bool> ctx_store_was_null{true};
std::atomic<bool> read_succeeded{false};
std::string read_value;
std::mutex mu;
};
class ProbeNode : public GraphNode {
public:
ProbeNode(std::string n, std::shared_ptr<StoreProbe> probe,
std::string ns, std::string key)
: n_(std::move(n)), probe_(std::move(probe)),
ns_{std::move(ns)}, key_(std::move(key)) {}
// Use the v0.4 unified run() entry so we receive NodeInput
// (which carries RunContext via in.ctx).
asio::awaitable<NodeOutput> run(NodeInput in) override {
probe_->ctx_store_was_null.store(in.ctx.store == nullptr,
std::memory_order_release);
if (in.ctx.store) {
auto v = in.ctx.store->get(ns_, key_);
if (v) {
std::lock_guard<std::mutex> lk(probe_->mu);
probe_->read_value = v->value.is_string()
? v->value.get<std::string>()
: v->value.dump();
probe_->read_succeeded.store(true, std::memory_order_release);
}
}
NodeOutput out;
out.writes.push_back({"hits", json(1)});
co_return out;
}
std::string get_name() const override { return n_; }
private:
std::string n_;
std::shared_ptr<StoreProbe> probe_;
Namespace ns_;
std::string key_;
};
json make_def() {
return {
{"name", "probe_graph"},
{"channels", {{"hits", {{"reducer", "overwrite"}}}}},
{"nodes", {{"probe", {{"type", "probe"}}}}},
{"edges", {
{{"from", "__start__"}, {"to", "probe"}},
{{"from", "probe"}, {"to", "__end__"}},
}},
};
}
} // namespace
TEST(RunContextStore, NullByDefaultWhenEngineHasNoStore) {
auto probe = std::make_shared<StoreProbe>();
NodeFactory::instance().register_type("probe",
[probe](const std::string& name, const json&, const NodeContext&) {
return std::make_unique<ProbeNode>(name, probe, "users", "alice");
});
NodeContext ctx;
auto engine = GraphEngine::compile(make_def(), ctx);
// No set_store call.
RunConfig cfg;
cfg.input = {{"hits", 0}};
engine->run(cfg);
EXPECT_TRUE(probe->ctx_store_was_null.load());
EXPECT_FALSE(probe->read_succeeded.load());
}
TEST(RunContextStore, PopulatedFromEngineSetStore) {
auto probe = std::make_shared<StoreProbe>();
auto store = std::make_shared<InMemoryStore>();
store->put(Namespace{"users"}, "alice",
json{{"role", "admin"}, {"city", "Seoul"}});
NodeFactory::instance().register_type("probe",
[probe](const std::string& name, const json&, const NodeContext&) {
return std::make_unique<ProbeNode>(name, probe, "users", "alice");
});
NodeContext ctx;
auto engine = GraphEngine::compile(make_def(), ctx);
engine->set_store(store);
RunConfig cfg;
cfg.input = {{"hits", 0}};
engine->run(cfg);
EXPECT_FALSE(probe->ctx_store_was_null.load());
EXPECT_TRUE(probe->read_succeeded.load());
EXPECT_NE(probe->read_value.find("Seoul"), std::string::npos);
}
TEST(RunContextStore, MultipleRunsSeeSameStore) {
// Repeat the populated case three times — same engine, same Store
// configured. Reading through ctx.store across runs should be
// stable.
//
// GraphEngine::compile() resolves the node factory once and reuses
// the same node instance across runs, so the probe captured by the
// factory closure is shared. We reset its flags between iterations
// and assert per-iteration that the most-recent run populated them
// again — that proves ctx.store is repopulated every dispatch.
auto store = std::make_shared<InMemoryStore>();
store->put(Namespace{"k"}, "v", json("stable"));
auto probe = std::make_shared<StoreProbe>();
NodeFactory::instance().register_type("probe",
[probe](const std::string& name, const json&, const NodeContext&) {
return std::make_unique<ProbeNode>(name, probe, "k", "v");
});
NodeContext ctx;
auto engine = GraphEngine::compile(make_def(), ctx);
engine->set_store(store);
for (int i = 0; i < 3; ++i) {
// Reset probe state so the assertion at the end of this
// iteration is about THIS run, not a stale value from i-1.
probe->ctx_store_was_null.store(true, std::memory_order_release);
probe->read_succeeded.store(false, std::memory_order_release);
{
std::lock_guard<std::mutex> lk(probe->mu);
probe->read_value.clear();
}
RunConfig cfg;
cfg.input = {{"hits", 0}};
engine->run(cfg);
EXPECT_FALSE(probe->ctx_store_was_null.load()) << "iter " << i;
EXPECT_TRUE(probe->read_succeeded.load()) << "iter " << i;
std::lock_guard<std::mutex> lk(probe->mu);
EXPECT_EQ(probe->read_value, "stable") << "iter " << i;
}
}