Skip to content
File

Blob: src/workerd/io/frankenvalue-test.c++

4.7 KB
1#include "frankenvalue.h"
2 
3#include <workerd/jsg/jsg-test.h>
4 
5#include <capnp/message.h>
6#include <kj/debug.h>
7#include <kj/test.h>
8 
9namespace workerd {
10namespace {
11 
12jsg::V8System v8System;
13class ContextGlobalObject: public jsg::Object, public jsg::ContextGlobal {};
14 
15enum class TestSerializationTag { TEST_SERIALIZABLE };
16 
17struct TestCapEntry final: public Frankenvalue::CapTableEntry {
18 int value;
19 
20 TestCapEntry(int value): value(value) {}
21 kj::Own<CapTableEntry> clone() override {
22 return kj::heap<TestCapEntry>(value);
23 }
24};
25 
26class TestSerializableCap: public jsg::Object {
27 public:
28 TestSerializableCap(int value): value(value) {}
29 
30 int toJSON() {
31 return value;
32 }
33 
34 JSG_RESOURCE_TYPE(TestSerializableCap) {
35 JSG_METHOD(toJSON);
36 }
37 
38 void serialize(jsg::Lock& js, jsg::Serializer& serializer) {
39 auto& handler = KJ_ASSERT_NONNULL(serializer.getExternalHandler());
40 auto externalHandler = dynamic_cast<Frankenvalue::CapTableBuilder*>(&handler);
41 KJ_ASSERT(externalHandler != nullptr);
42 serializer.writeRawUint32(externalHandler->add(kj::heap<TestCapEntry>(value)));
43 }
44 
45 static jsg::Ref<TestSerializableCap> deserialize(
46 jsg::Lock& js, TestSerializationTag tag, jsg::Deserializer& deserializer) {
47 auto& handler = KJ_REQUIRE_NONNULL(deserializer.getExternalHandler());
48 auto externalHandler = dynamic_cast<Frankenvalue::CapTableReader*>(&handler);
49 KJ_REQUIRE(externalHandler != nullptr);
50 
51 auto& cap = KJ_REQUIRE_NONNULL(externalHandler->get(deserializer.readRawUint32()));
52 auto typedCap = dynamic_cast<TestCapEntry*>(&cap);
53 KJ_REQUIRE(typedCap != nullptr);
54 
55 return js.alloc<TestSerializableCap>(typedCap->value);
56 }
57 
58 JSG_SERIALIZABLE(TestSerializationTag::TEST_SERIALIZABLE);
59 
60 private:
61 int value;
62};
63 
64struct TestContext: public ContextGlobalObject {
65 JSG_RESOURCE_TYPE(TestContext) {}
66};
67JSG_DECLARE_ISOLATE_TYPE(TestIsolate, TestContext, TestSerializableCap);
68 
69KJ_TEST("Frankenvalue") {
70 jsg::test::Evaluator<TestContext, TestIsolate> e(v8System);
71 
72 // Silence compiler warning.
73 (void)TestSerializableCap::jsgSerializeOneway;
74 (void)TestSerializableCap::jsgSerializeOldTags;
75 
76 e.run([&](auto& lock) {
77 jsg::Lock& js = lock;
78 auto makeSerializableCap = [&](int i) {
79 return jsg::JsValue(lock.wrap(js.v8Context(), js.alloc<TestSerializableCap>(i)));
80 };
81 
82 // Create a value based on JSON.
83 Frankenvalue value = Frankenvalue::fromJson(kj::str(R"({"baz": 321, "qux": "xyz"})"_kj));
84 
85 // prop1 is empty.
86 value.setProperty(kj::str("prop1"), {});
87 
88 // prop2 is a V8-serialized value.
89 value.setProperty(kj::str("prop2"), [&]() {
90 auto obj = js.obj();
91 obj.set(js, "foo", js.num(123));
92 obj.set(js, "bar", js.str("abc"_kj));
93 obj.set(js, "baz", makeSerializableCap(234));
94 obj.set(js, "qux", makeSerializableCap(345));
95 
96 auto result = Frankenvalue::fromJs(js, obj);
97 
98 result.setProperty(kj::str("nested"), [&]() {
99 auto nested = js.obj();
100 nested.set(js, "corge", js.num(222));
101 nested.set(js, "grault",
102 jsg::JsValue(lock.wrap(js.v8Context(), js.alloc<TestSerializableCap>(333))));
103 return Frankenvalue::fromJs(js, nested);
104 }());
105 result.setProperty(kj::str("nested2"), [&]() {
106 auto nested = js.obj();
107 nested.set(js, "garply", js.num(444));
108 nested.set(js, "waldo",
109 jsg::JsValue(lock.wrap(js.v8Context(), js.alloc<TestSerializableCap>(555))));
110 return Frankenvalue::fromJs(js, nested);
111 }());
112 
113 return result;
114 }());
115 
116 KJ_ASSERT(value.getCapTable().size() == 4);
117 KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[0]).value == 234);
118 KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[1]).value == 345);
119 KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[2]).value == 333);
120 KJ_EXPECT(kj::downcast<TestCapEntry>(*value.getCapTable()[3]).value == 555);
121 
122 // Round trip through capnp.
123 {
124 capnp::MallocMessageBuilder message;
125 auto builder = message.initRoot<rpc::Frankenvalue>();
126 value.toCapnp(builder);
127 
128 kj::Vector<kj::Own<Frankenvalue::CapTableEntry>> newCapTable;
129 for (auto& entry: value.getCapTable()) {
130 newCapTable.add(entry->clone());
131 }
132 
133 value = Frankenvalue::fromCapnp(builder.asReader(), kj::mv(newCapTable));
134 }
135 
136 // Use clone().
137 value = value.clone();
138 
139 // Back to JS, then JSON, then check that nothing was lost.
140 KJ_EXPECT(js.serializeJson(value.toJs(js)) ==
141 R"({"baz":321,"qux":"xyz","prop1":{},"prop2":{"foo":123,"bar":"abc","baz":234,"qux":345)"_kj
142 R"(,"nested":{"corge":222,"grault":333},"nested2":{"garply":444,"waldo":555}}})"_kj);
143 });
144}
145 
146} // namespace
147} // namespace workerd