Skip to content
File

Blob: src/workerd/tests/test-fixture.h

cpp177 lines
1// Copyright (c) 2023 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#pragma once
6 
7#include <workerd/api/memory-cache.h>
8#include <workerd/io/io-context.h>
9#include <workerd/io/worker.h>
10#include <workerd/jsg/jsg.h>
11#include <workerd/server/workerd.capnp.h>
12 
13#include <capnp/message.h>
14#include <kj/function.h>
15#include <kj/test.h>
16 
17namespace workerd {
18 
19// TestFixture is responsible for creating workerd environment during tests.
20// All the infrastructure is started in the constructor. It is accessed through run() method.
21struct TestFixture {
22 struct SetupParams {
23 // waitScope of outer IO loop. New IO will be set up if missing.
24 kj::Maybe<kj::WaitScope&> waitScope;
25 kj::Maybe<CompatibilityFlags::Reader> featureFlags;
26 kj::Maybe<kj::StringPtr> mainModuleSource;
27 // If set, make a stub of an Actor with the given id.
28 kj::Maybe<Worker::Actor::Id> actorId;
29 // If true, use real timers instead of mock timers that never advance.
30 // Requires waitScope to be kj::none (so that the fixture creates its own AsyncIoContext).
31 bool useRealTimers;
32 // If set, used instead of the default DummyIoChannelFactory when creating incoming requests.
33 // The factory receives the TimerChannel reference.
34 kj::Maybe<kj::Function<kj::Own<IoChannelFactory>(TimerChannel&)>> ioChannelFactory;
35 };
36 
37 TestFixture(SetupParams&& params = {.useRealTimers = false});
38 
39 struct V8Environment {
40 v8::Isolate* isolate;
41 };
42 
43 struct Environment: public V8Environment {
44 IoContext& context;
45 Worker::Lock& lock;
46 jsg::Lock& js;
47 CompatibilityFlags::Reader features;
48 };
49 
50 template <typename T>
51 struct RunReturnType {
52 using Type = T;
53 };
54 template <typename T>
55 struct RunReturnType<kj::Promise<T>> {
56 using Type = T;
57 };
58 
59 // Setup the incoming request and run given callback in worker's IO context.
60 // callback should accept const Environment& parameter and return Promise<T>|void.
61 // For void callbacks run waits for their completion, for promises waits for their resolution
62 // and returns the result.
63 template <typename CallBack>
64 auto runInIoContext(CallBack&& callback)
65 -> RunReturnType<decltype(callback(kj::instance<const Environment&>()))>::Type {
66 auto request = createIncomingRequest();
67 kj::WaitScope* waitScope;
68 KJ_IF_SOME(ws, this->waitScope) {
69 waitScope = &ws;
70 } else {
71 waitScope = &KJ_REQUIRE_NONNULL(io).waitScope;
72 }
73 
74 auto& context = request->getContext();
75 return context
76 .run([&](Worker::Lock& lock) {
77 // auto features = workerBundle.getFeatureFlags();
78 auto& js = jsg::Lock::from(lock.getIsolate());
79 Environment env = {{.isolate = lock.getIsolate()}, context, lock, js};
80 KJ_ASSERT(env.isolate == v8::Isolate::TryGetCurrent());
81 return callback(env);
82 }).wait(*waitScope);
83 }
84 
85 // Special void version of runInIoContext that ignores exceptions with given descriptions.
86 void runInIoContext(kj::Function<kj::Promise<void>(const Environment&)>&& callback,
87 kj::ArrayPtr<const kj::StringPtr> errorsToIgnore);
88 
89 struct Response {
90 uint statusCode;
91 kj::String body;
92 };
93 
94 // Performs HTTP request on the default module handler, and waits for full response.
95 Response runRequest(kj::HttpMethod method, kj::StringPtr url, kj::StringPtr body);
96 
97 private:
98 kj::Maybe<kj::WaitScope&> waitScope;
99 capnp::MallocMessageBuilder configArena;
100 workerd::server::config::Worker::Reader config;
101 kj::Maybe<kj::AsyncIoContext> io;
102 capnp::MallocMessageBuilder workerBundleArena;
103 kj::Own<kj::Timer> timer;
104 kj::Own<TimerChannel> timerChannel;
105 kj::Own<kj::EntropySource> entropySource;
106 kj::Maybe<kj::Own<Worker::Actor>> actor;
107 capnp::ByteStreamFactory byteStreamFactory;
108 kj::HttpHeaderTable::Builder headerTableBuilder;
109 ThreadContext::HeaderIdBundle threadContextHeaderBundle;
110 capnp::HttpOverCapnpFactory httpOverCapnpFactory;
111 ThreadContext threadContext;
112 kj::Own<Worker::ValidationErrorReporter> errorReporter;
113 kj::Own<api::MemoryCacheProvider> memoryCacheProvider;
114 v8::IsolateGroup isolateGroup;
115 kj::Own<Worker::Api> api;
116 kj::Own<Worker::Isolate> workerIsolate;
117 kj::Own<Worker::Script> workerScript;
118 kj::Own<Worker> worker;
119 kj::Own<kj::TaskSet::ErrorHandler> errorHandler;
120 kj::TaskSet waitUntilTasks;
121 kj::Own<kj::HttpHeaderTable> headerTable;
122 kj::Maybe<kj::Function<kj::Own<IoChannelFactory>(TimerChannel&)>> ioChannelFactory;
123 
124 kj::Own<IoContext::IncomingRequest> createIncomingRequest();
125 
126 public:
127 // Default IoChannelFactory used by tests. Exposed so tests can subclass it
128 // and override individual methods (e.g. startSubrequest for socket connect tests).
129 struct DummyIoChannelFactory: public IoChannelFactory {
130 virtual ~DummyIoChannelFactory() = default;
131 DummyIoChannelFactory(TimerChannel& timer): timer(timer) {}
132 
133 void abortIsolate(kj::StringPtr reason) override {
134 KJ_FAIL_ASSERT("no abortIsolate");
135 }
136 
137 kj::Own<WorkerInterface> startSubrequest(uint channel, SubrequestMetadata metadata) override {
138 KJ_FAIL_ASSERT("no subrequests");
139 }
140 kj::Own<SubrequestChannel> getSubrequestChannel(uint channel,
141 kj::Maybe<Frankenvalue> props,
142 kj::Maybe<VersionRequest> versionRequest) override {
143 KJ_FAIL_ASSERT("no subrequests");
144 }
145 capnp::Capability::Client getCapability(uint channel) override {
146 KJ_FAIL_ASSERT("no capabilities");
147 }
148 // Out-of-line because it references file-local MockCacheClient in test-fixture.c++.
149 kj::Own<CacheClient> getCache() override;
150 TimerChannel& getTimer() override {
151 return timer;
152 }
153 kj::Promise<void> writeLogfwdr(
154 uint channel, kj::FunctionParam<void(capnp::AnyPointer::Builder)> buildMessage) override {
155 KJ_FAIL_ASSERT("no log channels");
156 }
157 kj::Own<ActorChannel> getGlobalActor(uint channel,
158 const ActorIdFactory::ActorId& id,
159 kj::Maybe<kj::String> locationHint,
160 ActorGetMode mode,
161 bool enableReplicaRouting,
162 ActorRoutingMode routingMode,
163 SpanParent parentSpan,
164 kj::Maybe<ActorVersion> version) override {
165 KJ_FAIL_REQUIRE("no actor channels");
166 }
167 kj::Own<ActorChannel> getColoLocalActor(
168 uint channel, kj::StringPtr id, SpanParent parentSpan) override {
169 KJ_FAIL_REQUIRE("no actor channels");
170 }
171 
172 TimerChannel& timer;
173 };
174};
175 
176} // namespace workerd