Skip to content
File

Blob: src/workerd/jsg/iterator-test.c++

9.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 "jsg-test.h"
6 
7namespace workerd::jsg::test {
8namespace {
9 
10V8System v8System;
11 
12struct GeneratorContext: public Object, public ContextGlobal {
13 
14 kj::Array<kj::String> generatorTest(Lock& js, Generator<kj::String> generator) {
15 kj::Vector<kj::String> items;
16 while (true) {
17 KJ_IF_SOME(item, generator.next(js)) {
18 items.add(kj::mv(item));
19 } else {
20 break;
21 }
22 }
23 return items.releaseAsArray();
24 }
25 
26 uint generatorErrorTest(Lock& js, Generator<kj::String> generator) {
27 uint count = 0;
28 
29 // First call to next() should succeed and return "a"
30 KJ_IF_SOME(val, generator.next(js)) {
31 KJ_ASSERT(val == "a");
32 ++count;
33 }
34 
35 // Second call - we'll throw an error, which should trigger the generator's
36 // throw handler (the catch block), which yields "c"
37 KJ_IF_SOME(val, generator.throw_(js, js.v8Ref<v8::Value>(js.str("boom"_kj)))) {
38 KJ_ASSERT(val == "c");
39 ++count;
40 }
41 
42 return count;
43 }
44 
45 uint sequenceOfSequenceTest(Lock& js, Sequence<Sequence<kj::String>> sequence) {
46 uint count = 0;
47 for (auto& mem: sequence) {
48 count += mem.size();
49 }
50 return count;
51 }
52 
53 uint asyncGeneratorTest(Lock& js, AsyncGenerator<kj::String> generator) {
54 uint count = 0;
55 bool finished = false;
56 
57 // Get first item
58 generator.next(js)
59 .then(js, [&count, &generator](auto& js, auto value) {
60 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "a");
61 ++count;
62 
63 // After getting first item, call return_() to terminate early
64 return generator.return_(js, kj::str("foo")).then(js, [&count](auto& js, auto value) {
65 // return_() should give us back "foo" and mark as done
66 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "foo");
67 ++count;
68 return js.resolvedPromise();
69 });
70 }).then(js, [&finished](auto& js) {
71 finished = true;
72 return js.resolvedPromise();
73 });
74 
75 js.runMicrotasks();
76 
77 KJ_ASSERT(finished);
78 KJ_ASSERT(count == 2);
79 
80 return count;
81 }
82 
83 uint asyncGeneratorErrorTest(Lock& js, AsyncGenerator<kj::String> generator) {
84 uint count = 0;
85 bool finished = false;
86 
87 // First call to next() should succeed and return "a"
88 generator.next(js)
89 .then(js, [&count, &generator](auto& js, auto value) {
90 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "a");
91 ++count;
92 
93 // Second call - throw an error, which should trigger the generator's
94 // throw handler (the catch block), which yields "c"
95 return generator.throw_(js, js.template v8Ref<v8::Value>(js.str("boom"_kj)))
96 .then(js, [&count](auto& js, auto value) {
97 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "c");
98 ++count;
99 return js.resolvedPromise();
100 });
101 }).then(js, [&finished](auto& js) {
102 finished = true;
103 return js.resolvedPromise();
104 });
105 
106 js.runMicrotasks();
107 
108 KJ_ASSERT(finished);
109 KJ_ASSERT(count == 2);
110 
111 return count;
112 }
113 
114 void manualAsyncGeneratorTest(Lock& js, AsyncGenerator<kj::String> generator) {
115 uint calls = 0;
116 generator.next(js).then(js, [&calls](auto& js, auto value) {
117 calls++;
118 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "a");
119 return js.resolvedPromise();
120 });
121 
122 generator.next(js).then(js, [&calls](auto& js, auto value) {
123 calls++;
124 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "b");
125 return js.resolvedPromise();
126 });
127 
128 generator.next(js).then(js, [&calls](auto& js, auto value) {
129 calls++;
130 KJ_ASSERT(value == kj::none);
131 });
132 
133 js.runMicrotasks();
134 KJ_ASSERT(calls == 3);
135 }
136 
137 void manualAsyncGeneratorTestEarlyReturn(Lock& js, AsyncGenerator<kj::String> generator) {
138 uint calls = 0;
139 generator.next(js).then(js, [&calls](auto& js, auto value) {
140 calls++;
141 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "a");
142 return js.resolvedPromise();
143 });
144 
145 generator.return_(js, kj::str("foo")).then(js, [&calls](auto& js, auto value) {
146 calls++;
147 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "foo");
148 return js.resolvedPromise();
149 });
150 
151 generator.next(js).then(js, [&calls](auto& js, auto value) {
152 calls++;
153 KJ_ASSERT(value == kj::none);
154 });
155 
156 js.runMicrotasks();
157 KJ_ASSERT(calls == 3);
158 }
159 
160 void manualAsyncGeneratorTestThrow(Lock& js, AsyncGenerator<kj::String> generator) {
161 uint calls = 0;
162 generator.next(js).then(js, [&calls](auto& js, auto value) {
163 calls++;
164 KJ_ASSERT(KJ_ASSERT_NONNULL(value) == "a");
165 return js.resolvedPromise();
166 });
167 
168 // The default implementation of throw on the Async generator will result in a
169 // rejected promise being returned by generator.throw_(...)
170 generator.throw_(js, js.v8Ref<v8::Value>(js.str("boom"_kj)))
171 .catch_(js, [&calls](jsg::Lock& js, jsg::Value exception) {
172 calls++;
173 return kj::Maybe<kj::String>(kj::none);
174 });
175 
176 generator.next(js).then(js, [&calls](auto& js, auto value) {
177 calls++;
178 KJ_ASSERT(value == kj::none);
179 });
180 
181 js.runMicrotasks();
182 KJ_ASSERT(calls == 3);
183 }
184 
185 struct Test {
186 int foo;
187 JSG_STRUCT(foo);
188 };
189 
190 void generatorWrongType(Lock& js, Generator<Test> generator) {
191 // This should throw a type error when trying to unwrap the value
192 generator.next(js);
193 }
194 
195 void generatorReturnNotCallable(Lock& js, Generator<kj::String> generator) {
196 // Per the GetMethod spec, if the 'return' property exists on the iterator
197 // but is not callable, calling return_() should throw a TypeError.
198 generator.return_(js);
199 }
200 
201 void asyncGeneratorReturnNotCallable(Lock& js, AsyncGenerator<kj::String> generator) {
202 // Per the GetMethod spec, if the 'return' property exists on the async iterator
203 // but is not callable, calling return_() should produce a rejected promise with
204 // a TypeError.
205 bool gotRejection = false;
206 generator.return_(js).catch_(js, [&gotRejection](jsg::Lock& js, jsg::Value exception) {
207 gotRejection = true;
208 return kj::Maybe<kj::String>(kj::none);
209 });
210 js.runMicrotasks();
211 KJ_ASSERT(gotRejection);
212 }
213 
214 JSG_RESOURCE_TYPE(GeneratorContext) {
215 JSG_METHOD(generatorTest);
216 JSG_METHOD(generatorErrorTest);
217 JSG_METHOD(sequenceOfSequenceTest);
218 JSG_METHOD(generatorWrongType);
219 JSG_METHOD(generatorReturnNotCallable);
220 JSG_METHOD(asyncGeneratorTest);
221 JSG_METHOD(asyncGeneratorErrorTest);
222 JSG_METHOD(manualAsyncGeneratorTest);
223 JSG_METHOD(manualAsyncGeneratorTestEarlyReturn);
224 JSG_METHOD(manualAsyncGeneratorTestThrow);
225 JSG_METHOD(asyncGeneratorReturnNotCallable);
226 }
227};
228JSG_DECLARE_ISOLATE_TYPE(GeneratorIsolate, GeneratorContext, GeneratorContext::Test);
229 
230KJ_TEST("Generator works") {
231 Evaluator<GeneratorContext, GeneratorIsolate> e(v8System);
232 
233 e.expectEval("generatorTest([undefined,2,3])", "object", "undefined,2,3");
234 
235 e.expectEval(
236 "function* gen() { try { yield 'a'; yield 'b'; yield 'c'; } finally { yield 'd'; } };"
237 "generatorTest(gen())",
238 "object", "a,b,c,d");
239 
240 e.expectEval("function* gen() { try { yield 'a'; yield 'b'; } catch { yield 'c' } }; "
241 "generatorErrorTest(gen())",
242 "number", "2");
243 
244 e.expectEval("sequenceOfSequenceTest([['a','b'],['c', undefined]])", "number", "4");
245 
246 e.expectEval("generatorWrongType(['a'])", "throws",
247 "TypeError: Incorrect type: the provided value is not of type 'Test'.");
248 
249 // Per the GetMethod spec, if the 'return' property exists but is not callable,
250 // calling return_() should throw a TypeError.
251 e.expectEval("var iter = { [Symbol.iterator]() { return this; }, "
252 "next() { return { value: 'a', done: false }; }, "
253 "return: 42 }; "
254 "generatorReturnNotCallable(iter)",
255 "throws", "TypeError: Property 'return' is not a function");
256}
257 
258KJ_TEST("AsyncGenerator works") {
259 Evaluator<GeneratorContext, GeneratorIsolate> e(v8System);
260 
261 e.expectEval(
262 "async function* foo() { yield 'a'; yield 'b'; }; asyncGeneratorTest(foo());", "number", "2");
263 
264 e.expectEval("async function* gen() { try { yield 'a'; yield 'b'; } catch { yield 'c' } }; "
265 "asyncGeneratorErrorTest(gen())",
266 "number", "2");
267 
268 e.expectEval("manualAsyncGeneratorTest(async function* foo() { yield 'a'; yield 'b'; }())",
269 "undefined", "undefined");
270 
271 e.expectEval("manualAsyncGeneratorTestEarlyReturn(async function* foo() "
272 "{ yield 'a'; yield 'b'; }())",
273 "undefined", "undefined");
274 
275 // e.expectEval("manualAsyncGeneratorTestThrow(async function* foo() { yield 'a'; yield 'b'; }())",
276 // "undefined", "undefined");
277 
278 // Per the GetMethod spec, if the 'return' property exists but is not callable,
279 // calling return_() should produce a rejected promise with a TypeError.
280 e.expectEval("var iter = { [Symbol.asyncIterator]() { return this; }, "
281 "next() { return Promise.resolve({ value: 'a', done: false }); }, "
282 "return: 42 }; "
283 "asyncGeneratorReturnNotCallable(iter)",
284 "undefined", "undefined");
285}
286 
287} // namespace
288} // namespace workerd::jsg::test