Repository navigation
Expand file tree
/
Copy pathruntime_graph_tests.cpp
More file actions
251 lines (243 loc) · 12.4 KB
/
Copy pathruntime_graph_tests.cpp
File metadata and controls
251 lines (243 loc) · 12.4 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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#include <teaql/runtime/graph.hpp>
#include <teaql/runtime/context.hpp>
#include <teaql/runtime/module.hpp>
#include <iostream>
#include <type_traits>
using namespace teaql;
static void check(bool v, const char* message) {
if (!v) {
throw std::runtime_error(message);
}
}
template <class F>
void rejects(F f) {
bool caught = false;
try {
f();
} catch (const std::logic_error&) {
caught = true;
}
check(caught, "invalid trace borrowing rejected");
}
static_assert(!std::is_same_v<EntityValues, MutationValues>);
static_assert(!std::is_same_v<EntityValues, EntitySnapshot>);
static GraphNode node(Memory r) {
GraphNode root(std::allocator_arg, Allocator(r), "Order with a long entity name");
root.value("id", 7)
.value("name", "a long value allocated inside the user context")
.with_comment("");
root.dirty_fields.emplace(r);
root.dirty_fields->emplace("long dirty field allocated inside the user context");
root.original_values.emplace(r);
root.original_values->fields.emplace(String("original", r),
Value(r, "old value allocated inside user"));
GraphNode child(std::allocator_arg, Allocator(r), "Child");
child.value("id", Value(r));
root.relation("children", child).with_relations("empty", {});
return root;
}
static void lifetimes() {
GlobalContext global;
auto source = global.make_user_context();
auto target = global.make_user_context();
auto r = source->memory_resource();
auto& root = source->make<GraphNode>(node(r));
auto& retained = global.make<GraphNode>(root);
auto& module = source->make<RuntimeModule>();
module.initial_graph(root).root_graph(root);
module.apply_to(*target);
root.entity = "changed after install";
check(target->initial_graphs().size() == 1 && target->root_graphs().size() == 1,
"module installs both graph collections");
auto& first =
source->make<TraceNode>(TraceKind::Entity, "Order", "root comment with allocated text", 7);
auto& last =
source->make<TraceNode>(TraceKind::Comment, "Child", "child comment with allocated text");
auto& parent = source->make<TraceScopeToken>(nullptr, first, 1);
auto& token = source->make<TraceScopeToken>(&parent, last, 2);
auto& plan = source->make<GraphMutationPlan>();
auto& copied = global.make<GraphMutationPlan>();
{
MutationValues values(r);
values.fields.emplace(String("payload", r), Value(r, "payload retained after close"));
Vector<String> fields(r);
fields.emplace_back("name");
std::optional<EntitySnapshot> old;
old.emplace(r);
old->fields.emplace(String("name", r), Value(r, "old"));
plan.planned_root = detail::clone_in(r, root);
plan.visited_nodes.emplace(String("Order", r),
String("identity with a long allocated value", r));
plan.push("Order", GraphMutationKind::Update, values, fields, &token, old);
plan.push("Order", GraphMutationKind::Update, values, fields, &token, old);
plan.rebuild_batches();
check(plan.items[0].scope_token == &token && plan.batches[0].items[0].scope_token == &token,
"same-domain token copies preserve constant-time borrowing");
copied = detail::clone_in(global.memory_resource(), plan);
check(copied == plan, "deep-copied plans retain value equality");
check(copied.items[0].scope_token != &token &&
copied.items[0].scope_token->parent() != &parent,
"cross-domain tokens recursively clone parents");
rejects([&] {
global.make<TraceScopeToken>(&token, last, 3);
});
rejects([&] {
target->make<TraceScopeToken>(&token, last, 3);
});
} // Caller-owned PMR values destruct before their resource closes.
source->close();
check(retained.entity == "Order with a long entity name" && retained.comment &&
retained.comment->empty(),
"Global graph retained original content including Some empty comment");
check(retained.dirty_fields->begin()->get_allocator().resource() == global.memory_resource(),
"nested set allocator propagated");
check(
retained.relations.find(std::string_view("children"))->second[0].values.memory_resource() ==
global.memory_resource(),
"nested graph values copied");
check(copied.items[0].scope_token->recover_trace_chain(global.memory_resource()).size() == 2,
"persistent trace remains valid after source close");
check(copied.visited_nodes.begin()->second.get_allocator().resource() ==
global.memory_resource(),
"visited identity strings propagated");
check(target->initial_graphs()[0].original_values->fields.size() == 1,
"Context graph snapshot independent of source module");
target->set_root_graphs(target->root_graphs());
check(target->root_graphs().size() == 1,
"replacing from own borrowed slice deep-copies before replacement");
auto& empty = global.make<RuntimeModule>();
empty.apply_to(*target);
check(target->initial_graphs().empty() && target->root_graphs().empty(),
"empty module replaces graphs");
check(source->allocation_stats().live_blocks == 0, "all source graph blocks returned");
}
static void dynamic_graph_components() {
GlobalContext global;
auto user_context = global.make_user_context();
auto r = user_context->memory_resource();
GraphNode* retained = nullptr;
{
auto make_child = [&](std::string_view revision, bool selected) {
const std::array fields{
DynamicFieldDefinitions::Field{"long_dynamic_title", DataType::Text}};
auto definitions = DynamicFieldDefinitions::from_fields(r, "Child", revision, fields);
check(bool(definitions), "valid dynamic graph definitions");
auto& managed = user_context->make<DynamicFieldDefinitions>(definitions.value());
Value::Object values(r);
if (selected) {
values.emplace(String("long_dynamic_title", r), Value(r));
}
auto payload = DynamicFieldValues::from_values(r, managed, values);
check(bool(payload), "valid dynamic graph values");
GraphNode child(std::allocator_arg, Allocator(r), "Child");
child.dynamic_fields = detail::make_box<DynamicFieldValues>(r, payload.value());
return child;
};
auto child = make_child("revision_one", true);
auto changed_definition = make_child("revision_two", true);
check(child.dynamic_fields->values() == changed_definition.dynamic_fields->values() &&
child.dynamic_fields->selected_codes() ==
changed_definition.dynamic_fields->selected_codes() &&
!(child == changed_definition),
"graph equality observes definitions independently of values and selection");
auto empty = make_child("revision_one", false);
check(child.dynamic_fields->definitions() == empty.dynamic_fields->definitions() &&
empty.dynamic_fields->values().empty() &&
empty.dynamic_fields->selected_codes().empty() && !(child == empty),
"selected NULL differs from an unselected dynamic field");
GraphNode absent(std::allocator_arg, Allocator(r), "Child");
check(!(absent == empty), "absent payload differs from present empty payload");
GraphNode root(std::allocator_arg, Allocator(r), "Parent");
root.relation("children", child);
retained = &global.make<GraphNode>(root);
check(*retained == root, "recursive dynamic graph clone compares equal");
auto& nested = root.relations.find("children")->second.front();
check(bool(nested.dynamic_fields->assign(
"long_dynamic_title", Value(r, "changed child value with allocated storage"))),
"nested dynamic assignment succeeds");
check(!(*retained == root), "recursive graph equality observes child value changes");
check(bool(nested.dynamic_fields->erase("long_dynamic_title")) && nested == empty,
"deletion restores unselected geometry and values together");
}
user_context->close();
const auto& child = retained->relations.find("children")->second.front();
check(child.dynamic_fields && child.dynamic_fields->values().size() == 1 &&
child.dynamic_fields->values().begin()->second.is_null() &&
child.dynamic_fields->selected_codes().size() == 1,
"Global recursive copy retains selected NULL after source close");
check(child.memory_resource() == global.memory_resource() &&
child.dynamic_fields->memory_resource() == global.memory_resource() &&
child.dynamic_fields->definitions().memory_resource() == global.memory_resource() &&
child.dynamic_fields->selected_codes().begin()->get_allocator().resource() ==
global.memory_resource(),
"recursive dynamic values definitions and selection belong to destination Context");
check(user_context->allocation_stats().live_blocks == 0,
"recursive source dynamic graph releases every arena block");
}
static void trace_and_conversion() {
GlobalContext global;
auto user = global.make_user_context();
auto r = user->memory_resource();
Record record(r);
record.emplace(String("z", r), Value(r, 1));
record.emplace(String("a", r), Value(r));
CompactRow row(std::allocator_arg, Allocator(r), record);
EntityValues values(std::allocator_arg, Allocator(r), row);
auto mutation = values.into_mutation_values(r);
EntityValues back(std::allocator_arg, Allocator(r), mutation);
check(values == back && values.fields == record,
"row and command conversions preserve independent entity state");
Vector<String> excluded(r);
excluded.emplace_back("z");
excluded.emplace_back("missing");
auto fields = sorted_update_fields(r, values, excluded);
check(fields.size() == 1 && fields[0] == "a",
"sorted field filter uses exact exclusions including null values");
TraceNode first(std::allocator_arg, Allocator(r), TraceKind::Entity, "Order", "first");
TraceNode empty(std::allocator_arg, Allocator(r), TraceKind::Relation, "Child", "");
TraceNode spaces(std::allocator_arg, Allocator(r), TraceKind::Comment, "Child", " ");
ScopedCommentNode a(std::allocator_arg, Allocator(r), nullptr, first),
b(std::allocator_arg, Allocator(r), &a, empty),
c(std::allocator_arg, Allocator(r), &b, spaces);
auto chain = c.to_trace_chain(r);
check(chain.size() == 1 && chain[0] == first,
"stack trace scopes omit whitespace-only comments as in 5.0.6");
check(graph_mutation_kind_for_update(true) == GraphMutationKind::Update &&
graph_mutation_kind_for_update(false) == GraphMutationKind::Create,
"mutation kind selection");
GraphMutationPlan overflow(std::allocator_arg, Allocator(r));
overflow.next_item_index = ~std::uint64_t(0);
bool threw = false;
try {
overflow.push("Order", GraphMutationKind::Create, mutation, {});
} catch (const std::overflow_error&) {
threw = true;
}
#ifndef NDEBUG
check(threw && overflow.items.empty() && overflow.next_item_index == ~std::uint64_t(0),
"Debug preserves checked source overflow before item insertion");
#else
check(!threw && overflow.items.size() == 1 && overflow.next_item_index == 0 &&
overflow.items[0].item_index == ~std::uint64_t(0),
"Release preserves source wrapping counter");
#endif
auto& p = user->make<TraceScopeToken>(nullptr, first, 0);
auto& q = user->make<TraceScopeToken>(&p, empty, 1);
auto& t = user->make<TraceScopeToken>(&q, spaces, 2);
auto persistent = t.recover_trace_chain(r);
check(persistent.size() == 1 && persistent.front() == first,
"persistent trace scopes omit whitespace-only comments as in 5.0.6");
}
int main() {
try {
auto* old = std::pmr::set_default_resource(std::pmr::null_memory_resource());
lifetimes();
dynamic_graph_components();
trace_and_conversion();
std::pmr::set_default_resource(old);
std::cout << "Runtime graph contracts passed\n";
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
return 1;
}
}