Skip to content
File

Blob: src/workerd/api/actor-state-test.c++

5.1 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include <workerd/api/actor-state.h>
6#include <workerd/api/util.h>
7#include <workerd/jsg/jsg-test.h>
8#include <workerd/jsg/jsg.h>
9#include <workerd/jsg/ser.h>
10#include <workerd/jsg/setup.h>
11 
12#include <capnp/message.h>
13#include <capnp/rpc-twoparty.h>
14#include <capnp/rpc.h>
15#include <kj/encoding.h>
16#include <kj/test.h>
17 
18#include <fstream>
19#include <iostream>
20 
21namespace workerd::api {
22namespace {
23 
24jsg::V8System v8System;
25 
26struct ActorStateContext: public jsg::Object, public jsg::ContextGlobal {
27 JSG_RESOURCE_TYPE(ActorStateContext) {}
28};
29JSG_DECLARE_ISOLATE_TYPE(ActorStateIsolate, ActorStateContext);
30 
31KJ_TEST("v8 serialization version tag hasn't changed") {
32 jsg::test::Evaluator<ActorStateContext, ActorStateIsolate> e(v8System);
33 e.getIsolate().runInLockScope([&](ActorStateIsolate::Lock& isolateLock) {
34 JSG_WITHIN_CONTEXT_SCOPE(isolateLock,
35 isolateLock.newContext<ActorStateContext>().getHandle(isolateLock), [&](jsg::Lock& js) {
36 auto buf = serializeV8Value(isolateLock, isolateLock.boolean(true));
37 
38 // Confirm that a version header is appropriately written and that it contains the expected
39 // current version. When the version increases, we need to write a v8 patch that allows it
40 // to continue writing data at the old version so that we can do a rolling upgrade without
41 // any bugs caused by old processes failing to read data written by new ones.
42 KJ_EXPECT(buf[0] == 0xFF);
43 KJ_EXPECT(buf[1] == 0x0F); // v8 serializer version
44 
45 // And this just confirms that the deserializer agrees on the version.
46 v8::ValueDeserializer deserializer(isolateLock.v8Isolate, buf.begin(), buf.size());
47 auto maybeHeader = deserializer.ReadHeader(isolateLock.v8Context());
48 KJ_EXPECT(jsg::check(maybeHeader));
49 KJ_EXPECT(deserializer.GetWireFormatVersion() == 15);
50 
51 // Just for kicks, make sure it deserializes properly too.
52 KJ_EXPECT(deserializeV8Value(isolateLock, "some-key"_kj, buf).isTrue());
53 });
54 });
55}
56 
57KJ_TEST("we support deserializing up to v15") {
58 jsg::test::Evaluator<ActorStateContext, ActorStateIsolate> e(v8System);
59 e.getIsolate().runInLockScope([&](ActorStateIsolate::Lock& isolateLock) {
60 JSG_WITHIN_CONTEXT_SCOPE(isolateLock,
61 isolateLock.newContext<ActorStateContext>().getHandle(isolateLock), [&](jsg::Lock& js) {
62 kj::Vector<kj::StringPtr> testCases;
63 testCases.add("54");
64 testCases.add("FF0D54");
65 testCases.add("FF0E54");
66 testCases.add("FF0F54");
67 
68 for (const auto& hexStr: testCases) {
69 auto dataIn = kj::decodeHex(hexStr.asArray());
70 KJ_EXPECT(deserializeV8Value(isolateLock, "some-key"_kj, dataIn).isTrue());
71 }
72 });
73 });
74}
75 
76// This is hacky, but we want to compare the old deserialization logic that's been in prod from when
77// actors went live through March 2022 to the new version of the deserialization logic and make sure
78// it works the same.
79// TODO(soon): Remove this. Ideally we can just fix the test below that attempts to read serialized
80// data and round-trip it back to storage to deal with the problem that it likes to read in "sparse"
81// JS arrays and write them back out as "dense" JS arrays, which breaks the equality check after
82// round-tripping a value.
83jsg::JsValue oldDeserializeV8Value(jsg::Lock& js, kj::ArrayPtr<const kj::byte> buf) {
84 jsg::Deserializer des(js, buf);
85 return des.readValue(js);
86}
87 
88KJ_TEST("wire format version does not change deserialization behavior on real data") {
89 // This test checks for the presence of a specially named file in the current working directory
90 // that contains lines of hex-encoded v8-serialized data. It processes one line at time,
91 // hex-decoding it and then testing deserializing/re-serializing it.
92 
93 std::ifstream file;
94 file.open("serialization-test-data.txt");
95 if (!file) {
96 KJ_LOG(WARNING, "skipping serialization test due to missing data file");
97 return;
98 }
99 
100 jsg::test::Evaluator<ActorStateContext, ActorStateIsolate> e(v8System);
101 e.getIsolate().runInLockScope([&](ActorStateIsolate::Lock& isolateLock) {
102 JSG_WITHIN_CONTEXT_SCOPE(isolateLock,
103 isolateLock.newContext<ActorStateContext>().getHandle(isolateLock), [&](jsg::Lock& js) {
104 // Read in data line by line and verify that it round trips (serializes and
105 // then deserializes) back to the exact same data as the input.
106 std::string hexStr;
107 const auto key = "some-key"_kj;
108 while (std::getline(file, hexStr)) {
109 auto dataIn = kj::decodeHex(kj::ArrayPtr(hexStr.c_str(), hexStr.size()));
110 KJ_EXPECT(!dataIn.hadErrors, kj::str(hexStr.c_str()));
111 
112 auto oldVal = oldDeserializeV8Value(isolateLock, dataIn);
113 auto oldOutput = serializeV8Value(isolateLock, oldVal);
114 
115 auto newVal = deserializeV8Value(isolateLock, key, dataIn);
116 auto newOutput = serializeV8Value(isolateLock, newVal);
117 KJ_EXPECT(oldOutput == newOutput, kj::str(hexStr.c_str()));
118 }
119 });
120 });
121}
122 
123} // namespace
124} // namespace workerd::api