Skip to content
File

Blob: src/workerd/tests/test-fixture-test.c++

5.5 KB
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#include "test-fixture.h"
6 
7#include <kj/test.h>
8 
9#include <memory>
10 
11#if KJ_HAS_COMPILER_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
12#include <sanitizer/lsan_interface.h>
13#define LSAN_ENABLED 1
14#else
15#define LSAN_ENABLED 0
16static int __lsan_do_recoverable_leak_check() {
17 return 0;
18}
19#endif
20 
21namespace workerd {
22namespace {
23 
24KJ_TEST("setup/destroy") {
25 TestFixture fixture;
26}
27 
28KJ_TEST("single void runInIoContext run") {
29 TestFixture fixture;
30 uint runCount = 0;
31 
32 fixture.runInIoContext([&](const TestFixture::Environment& env) { runCount++; });
33 
34 KJ_EXPECT(runCount == 1);
35}
36 
37KJ_TEST("single runInIoContext with promise result") {
38 TestFixture fixture;
39 uint runCount = 0;
40 
41 auto result = fixture.runInIoContext([&](const TestFixture::Environment& env) {
42 runCount++;
43 return kj::Promise<int>(42);
44 });
45 
46 KJ_EXPECT(runCount == 1);
47 KJ_EXPECT(result == 42);
48}
49 
50KJ_TEST("single runInIoContext with immediate result") {
51 TestFixture fixture;
52 uint runCount = 0;
53 
54 auto result = fixture.runInIoContext([&](const TestFixture::Environment& env) {
55 runCount++;
56 return 42;
57 });
58 
59 KJ_EXPECT(runCount == 1);
60 KJ_EXPECT(result == 42);
61}
62 
63KJ_TEST("3 runInIoContext runs") {
64 TestFixture fixture;
65 uint runCount = 0;
66 
67 for (uint i = 0; i < 3; i++) {
68 fixture.runInIoContext([&](const TestFixture::Environment& env) { runCount++; });
69 
70 KJ_EXPECT(runCount == i + 1);
71 }
72}
73 
74KJ_TEST("2 fixtures in a row with single runInIoContext run") {
75 uint runCount = 0;
76 
77 for (uint i = 0; i < 2; i++) {
78 TestFixture fixture;
79 fixture.runInIoContext([&](const TestFixture::Environment& env) { runCount++; });
80 
81 KJ_EXPECT(runCount == i + 1);
82 }
83}
84 
85KJ_TEST("runInIoContext consuming ignored kj::Exception") {
86 TestFixture fixture;
87 uint runCount = 0;
88 
89 fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {
90 runCount++;
91 KJ_FAIL_REQUIRE("test_error");
92 }, kj::arr("test_error"_kj));
93 
94 KJ_EXPECT(runCount == 1);
95}
96 
97KJ_TEST("runInIoContext re-throwing kj::Exception") {
98 TestFixture fixture;
99 uint runCount = 0;
100 uint exceptionCount = 0;
101 
102 try {
103 fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {
104 runCount++;
105 KJ_FAIL_REQUIRE("let_me_through");
106 }, kj::arr("test_error"_kj));
107 } catch (kj::Exception& e) {
108 KJ_EXPECT(e.getDescription() == "let_me_through"_kj);
109 exceptionCount++;
110 }
111 
112 KJ_EXPECT(exceptionCount == 1);
113 KJ_EXPECT(runCount == 1);
114}
115 
116KJ_TEST("runInIoContext re-throwing js exception") {
117 TestFixture fixture;
118 uint runCount = 0;
119 uint exceptionCount = 0;
120 
121 try {
122 fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {
123 runCount++;
124 env.js.throwException(env.js.error("let_me_through"));
125 }, kj::arr("test_error"_kj));
126 } catch (kj::Exception& e) {
127 KJ_EXPECT(e.getDescription() == "jsg.Error: let_me_through"_kj);
128 exceptionCount++;
129 }
130 
131 KJ_EXPECT(runCount == 1);
132 KJ_EXPECT(exceptionCount == 1);
133}
134 
135KJ_TEST("runInIoContext consuming ignored js exception") {
136 TestFixture fixture;
137 uint runCount = 0;
138 
139 fixture.runInIoContext([&](const TestFixture::Environment& env) -> kj::Promise<void> {
140 runCount++;
141 env.js.throwException(env.js.error("test_error"));
142 }, kj::arr("test_error"_kj));
143 
144 KJ_EXPECT(runCount == 1);
145}
146 
147KJ_TEST("runRequest") {
148 TestFixture fixture({.mainModuleSource = R"SCRIPT(
149 export default {
150 async fetch(request) {
151 const body = await(await request.blob()).text();
152 return new Response(`${request.method} ${request.url} ${body}`, { status: 202 });
153 },
154 };
155 )SCRIPT"_kj});
156 
157 auto result = fixture.runRequest(kj::HttpMethod::POST, "http://www.example.com"_kj, "TEST"_kj);
158 KJ_EXPECT(result.statusCode == 202);
159 KJ_EXPECT(result.body == "POST http://www.example.com TEST"_kj);
160}
161 
162KJ_TEST("module import failure") {
163 KJ_EXPECT_LOG(ERROR, "script startup threw exception");
164 
165 try {
166 TestFixture fixture({.mainModuleSource = R"SCRIPT(
167 import * from "bad-module";
168 
169 export default {
170 async fetch(request) {
171 return new Response("OK");
172 },
173 };
174 )SCRIPT"_kj});
175 
176 KJ_FAIL_REQUIRE("exception expected");
177 } catch (kj::Exception& e) {
178 KJ_EXPECT(e.getDescription() == "script startup threw exception"_kj);
179 }
180}
181 
182// This test mimics the fuzzer pattern where a static TestFixture is reused across iterations.
183// The Rust Realm is stored in V8's embedder data. In fuzzers with incremental leak detection, this can cause false positive leak
184// reports because LSAN checks between iterations while the static TestFixture is still alive.
185//
186// Note: We use unique_ptr here because the test must properly clean up before V8System's
187// static destructor runs. Fuzzers typically use raw `new` and rely on _exit() to skip
188// static destructors, but tests must clean up properly.
189KJ_TEST("static fixture with multiple iterations") {
190 static std::unique_ptr<TestFixture> fixture;
191 if (fixture == nullptr) {
192 fixture = std::make_unique<TestFixture>();
193 }
194 
195 uint runCount = 0;
196 
197 for (uint i = 0; i < 10; i++) {
198 fixture->runInIoContext([&](const TestFixture::Environment& env) { runCount++; });
199 }
200 
201 KJ_EXPECT(runCount == 10);
202 
203 if (LSAN_ENABLED) {
204 int leaks = __lsan_do_recoverable_leak_check();
205 KJ_EXPECT(leaks == 0, "LSAN detected leaks");
206 }
207}
208 
209} // namespace
210} // namespace workerd