pulumi/sdk/nodejs/tests/runtime/asyncIterableUtil.spec.ts

85 lines
3.4 KiB
TypeScript
Raw Permalink Normal View History

`StreamInvoke` should return `AsyncIterable` that completes A user who calls `StreamInvoke` probably expects the `AsyncIterable` that is returned to gracefully terminate. This is currently not the case. Where does something like this go wrong? A better question might be where any of this went right, because several days later, after wandering into civilization from the great Wilderness of Bugs, I must confess that I've forgotten if any of it had. `AsyncIterable` is a pull-based API. `for await (...)` will continuously call `next` ("pull") on the underlying `AsyncIterator` until the iterable is exhausted. But, gRPC's streaming-return API is _push_ based. That is to say, when a streaming RPC is called, data is provided by callback on the stream object, like: call.on("data", (thing: any) => {... do thing ...}); Our goal in `StreamInvoke` is to convert the push-based gRPC routines into the pull-based `AsyncIterable` retrun type. You may remember your CS theory this is one of those annoying "fundamental mismatches" in abstraction. So we're off to a good start. Until this point, we've depended on a library, `callback-to-async-iterator` to handle the details of being this bridge. Our trusting nature and innocent charm has mislead us. This library is not worthy of our trust. Instead of doing what we'd like it to do, it returns (in our case) an `AsyncIterable` that will never complete. Yes,, this `AsyncIterable` will patiently wait for eternity, which honestly is kind of poetic when you sit down in a nice bath and think about that fun time you considered eating your computer instead of finishing this idiotic bug. Indeed, this is the sort of bug that you wonder where it even comes from. Our query libraries? Why aren't these `finally` blocks executing? Is our language host terminating early? Is gRPC angry at me, and just passive-aggrssively not servicing some of my requests? Oh god I've been up for 48 hours, why is that wallpaper starting to move? And by the way, a fun interlude to take in an otherwise very productive week is to try to understand the gRPC streaming node client, which is code-gen'd, but which also takes the liberty of generating itself at runtime, so that gRPC is code-gen'ing a code-gen routine, which makes the whole thing un-introspectable, un-debuggable, and un-knowable. That's fine, I didn't need to understand any of this anyway, thanks friends. But we've come out the other side knowing that the weak link in this very sorry chain of incredibly weak links, is this dependency. This commit removes this dependency for a better monster: the one we know. It is at this time that I'd like to announce that I am quitting my job at Pulumi. I thank you all for the good times, but mostly, for taking this code over for me.
2019-11-11 23:00:46 +00:00
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { AsyncIterable } from "@pulumi/query/interfaces";
import * as assert from "assert";
import { PushableAsyncIterable } from "../../runtime/asyncIterableUtil";
async function enumerate<T>(ts: AsyncIterable<T>): Promise<T[]> {
const tss: T[] = [];
for await (const n of ts) {
tss.push(n);
}
return tss;
}
describe("PushableAsyncIterable", () => {
it("correctly produces empty sequence", async () => {
const queue = new PushableAsyncIterable<number>();
queue.complete();
assert.deepStrictEqual(await enumerate(queue), []);
});
`StreamInvoke` should return `AsyncIterable` that completes A user who calls `StreamInvoke` probably expects the `AsyncIterable` that is returned to gracefully terminate. This is currently not the case. Where does something like this go wrong? A better question might be where any of this went right, because several days later, after wandering into civilization from the great Wilderness of Bugs, I must confess that I've forgotten if any of it had. `AsyncIterable` is a pull-based API. `for await (...)` will continuously call `next` ("pull") on the underlying `AsyncIterator` until the iterable is exhausted. But, gRPC's streaming-return API is _push_ based. That is to say, when a streaming RPC is called, data is provided by callback on the stream object, like: call.on("data", (thing: any) => {... do thing ...}); Our goal in `StreamInvoke` is to convert the push-based gRPC routines into the pull-based `AsyncIterable` retrun type. You may remember your CS theory this is one of those annoying "fundamental mismatches" in abstraction. So we're off to a good start. Until this point, we've depended on a library, `callback-to-async-iterator` to handle the details of being this bridge. Our trusting nature and innocent charm has mislead us. This library is not worthy of our trust. Instead of doing what we'd like it to do, it returns (in our case) an `AsyncIterable` that will never complete. Yes,, this `AsyncIterable` will patiently wait for eternity, which honestly is kind of poetic when you sit down in a nice bath and think about that fun time you considered eating your computer instead of finishing this idiotic bug. Indeed, this is the sort of bug that you wonder where it even comes from. Our query libraries? Why aren't these `finally` blocks executing? Is our language host terminating early? Is gRPC angry at me, and just passive-aggrssively not servicing some of my requests? Oh god I've been up for 48 hours, why is that wallpaper starting to move? And by the way, a fun interlude to take in an otherwise very productive week is to try to understand the gRPC streaming node client, which is code-gen'd, but which also takes the liberty of generating itself at runtime, so that gRPC is code-gen'ing a code-gen routine, which makes the whole thing un-introspectable, un-debuggable, and un-knowable. That's fine, I didn't need to understand any of this anyway, thanks friends. But we've come out the other side knowing that the weak link in this very sorry chain of incredibly weak links, is this dependency. This commit removes this dependency for a better monster: the one we know. It is at this time that I'd like to announce that I am quitting my job at Pulumi. I thank you all for the good times, but mostly, for taking this code over for me.
2019-11-11 23:00:46 +00:00
it("correctly produces singleton sequence", async () => {
const queue = new PushableAsyncIterable<number>();
queue.push(1);
queue.complete();
assert.deepStrictEqual(await enumerate(queue), [1]);
});
`StreamInvoke` should return `AsyncIterable` that completes A user who calls `StreamInvoke` probably expects the `AsyncIterable` that is returned to gracefully terminate. This is currently not the case. Where does something like this go wrong? A better question might be where any of this went right, because several days later, after wandering into civilization from the great Wilderness of Bugs, I must confess that I've forgotten if any of it had. `AsyncIterable` is a pull-based API. `for await (...)` will continuously call `next` ("pull") on the underlying `AsyncIterator` until the iterable is exhausted. But, gRPC's streaming-return API is _push_ based. That is to say, when a streaming RPC is called, data is provided by callback on the stream object, like: call.on("data", (thing: any) => {... do thing ...}); Our goal in `StreamInvoke` is to convert the push-based gRPC routines into the pull-based `AsyncIterable` retrun type. You may remember your CS theory this is one of those annoying "fundamental mismatches" in abstraction. So we're off to a good start. Until this point, we've depended on a library, `callback-to-async-iterator` to handle the details of being this bridge. Our trusting nature and innocent charm has mislead us. This library is not worthy of our trust. Instead of doing what we'd like it to do, it returns (in our case) an `AsyncIterable` that will never complete. Yes,, this `AsyncIterable` will patiently wait for eternity, which honestly is kind of poetic when you sit down in a nice bath and think about that fun time you considered eating your computer instead of finishing this idiotic bug. Indeed, this is the sort of bug that you wonder where it even comes from. Our query libraries? Why aren't these `finally` blocks executing? Is our language host terminating early? Is gRPC angry at me, and just passive-aggrssively not servicing some of my requests? Oh god I've been up for 48 hours, why is that wallpaper starting to move? And by the way, a fun interlude to take in an otherwise very productive week is to try to understand the gRPC streaming node client, which is code-gen'd, but which also takes the liberty of generating itself at runtime, so that gRPC is code-gen'ing a code-gen routine, which makes the whole thing un-introspectable, un-debuggable, and un-knowable. That's fine, I didn't need to understand any of this anyway, thanks friends. But we've come out the other side knowing that the weak link in this very sorry chain of incredibly weak links, is this dependency. This commit removes this dependency for a better monster: the one we know. It is at this time that I'd like to announce that I am quitting my job at Pulumi. I thank you all for the good times, but mostly, for taking this code over for me.
2019-11-11 23:00:46 +00:00
it("correctly produces multiple sequence", async () => {
const queue = new PushableAsyncIterable<number>();
queue.push(1);
queue.push(2);
queue.push(3);
queue.complete();
assert.deepStrictEqual(await enumerate(queue), [1, 2, 3]);
});
`StreamInvoke` should return `AsyncIterable` that completes A user who calls `StreamInvoke` probably expects the `AsyncIterable` that is returned to gracefully terminate. This is currently not the case. Where does something like this go wrong? A better question might be where any of this went right, because several days later, after wandering into civilization from the great Wilderness of Bugs, I must confess that I've forgotten if any of it had. `AsyncIterable` is a pull-based API. `for await (...)` will continuously call `next` ("pull") on the underlying `AsyncIterator` until the iterable is exhausted. But, gRPC's streaming-return API is _push_ based. That is to say, when a streaming RPC is called, data is provided by callback on the stream object, like: call.on("data", (thing: any) => {... do thing ...}); Our goal in `StreamInvoke` is to convert the push-based gRPC routines into the pull-based `AsyncIterable` retrun type. You may remember your CS theory this is one of those annoying "fundamental mismatches" in abstraction. So we're off to a good start. Until this point, we've depended on a library, `callback-to-async-iterator` to handle the details of being this bridge. Our trusting nature and innocent charm has mislead us. This library is not worthy of our trust. Instead of doing what we'd like it to do, it returns (in our case) an `AsyncIterable` that will never complete. Yes,, this `AsyncIterable` will patiently wait for eternity, which honestly is kind of poetic when you sit down in a nice bath and think about that fun time you considered eating your computer instead of finishing this idiotic bug. Indeed, this is the sort of bug that you wonder where it even comes from. Our query libraries? Why aren't these `finally` blocks executing? Is our language host terminating early? Is gRPC angry at me, and just passive-aggrssively not servicing some of my requests? Oh god I've been up for 48 hours, why is that wallpaper starting to move? And by the way, a fun interlude to take in an otherwise very productive week is to try to understand the gRPC streaming node client, which is code-gen'd, but which also takes the liberty of generating itself at runtime, so that gRPC is code-gen'ing a code-gen routine, which makes the whole thing un-introspectable, un-debuggable, and un-knowable. That's fine, I didn't need to understand any of this anyway, thanks friends. But we've come out the other side knowing that the weak link in this very sorry chain of incredibly weak links, is this dependency. This commit removes this dependency for a better monster: the one we know. It is at this time that I'd like to announce that I am quitting my job at Pulumi. I thank you all for the good times, but mostly, for taking this code over for me.
2019-11-11 23:00:46 +00:00
it("correctly terminates outstanding operations afte complete", async () => {
const queue = new PushableAsyncIterable<number>();
const queueIter = queue[Symbol.asyncIterator]();
const terminates = async () => {
assert.deepStrictEqual(await queueIter.next(), { value: undefined, done: true });
assert.deepStrictEqual(await queueIter.next(), { value: undefined, done: true });
assert.deepStrictEqual(await queueIter.next(), { value: undefined, done: true });
};
queue.complete();
await terminates;
assert.deepStrictEqual(await queueIter.next(), { value: undefined, done: true });
});
it("correctly interleaves operations", async () => {
const queue = new PushableAsyncIterable<number>();
const queueIter = queue[Symbol.asyncIterator]();
queue.push(1);
queue.push(2);
assert.deepStrictEqual(await queueIter.next(), { value: 1, done: false });
queue.push(3);
assert.deepStrictEqual(await queueIter.next(), { value: 2, done: false });
assert.deepStrictEqual(await queueIter.next(), { value: 3, done: false });
queue.push(4);
queue.push(5);
queue.push(6);
queue.push(7);
assert.deepStrictEqual(await queueIter.next(), { value: 4, done: false });
assert.deepStrictEqual(await queueIter.next(), { value: 5, done: false });
assert.deepStrictEqual(await queueIter.next(), { value: 6, done: false });
assert.deepStrictEqual(await queueIter.next(), { value: 7, done: false });
queue.complete();
assert.deepStrictEqual(await queueIter.next(), { value: undefined, done: true });
});
`StreamInvoke` should return `AsyncIterable` that completes A user who calls `StreamInvoke` probably expects the `AsyncIterable` that is returned to gracefully terminate. This is currently not the case. Where does something like this go wrong? A better question might be where any of this went right, because several days later, after wandering into civilization from the great Wilderness of Bugs, I must confess that I've forgotten if any of it had. `AsyncIterable` is a pull-based API. `for await (...)` will continuously call `next` ("pull") on the underlying `AsyncIterator` until the iterable is exhausted. But, gRPC's streaming-return API is _push_ based. That is to say, when a streaming RPC is called, data is provided by callback on the stream object, like: call.on("data", (thing: any) => {... do thing ...}); Our goal in `StreamInvoke` is to convert the push-based gRPC routines into the pull-based `AsyncIterable` retrun type. You may remember your CS theory this is one of those annoying "fundamental mismatches" in abstraction. So we're off to a good start. Until this point, we've depended on a library, `callback-to-async-iterator` to handle the details of being this bridge. Our trusting nature and innocent charm has mislead us. This library is not worthy of our trust. Instead of doing what we'd like it to do, it returns (in our case) an `AsyncIterable` that will never complete. Yes,, this `AsyncIterable` will patiently wait for eternity, which honestly is kind of poetic when you sit down in a nice bath and think about that fun time you considered eating your computer instead of finishing this idiotic bug. Indeed, this is the sort of bug that you wonder where it even comes from. Our query libraries? Why aren't these `finally` blocks executing? Is our language host terminating early? Is gRPC angry at me, and just passive-aggrssively not servicing some of my requests? Oh god I've been up for 48 hours, why is that wallpaper starting to move? And by the way, a fun interlude to take in an otherwise very productive week is to try to understand the gRPC streaming node client, which is code-gen'd, but which also takes the liberty of generating itself at runtime, so that gRPC is code-gen'ing a code-gen routine, which makes the whole thing un-introspectable, un-debuggable, and un-knowable. That's fine, I didn't need to understand any of this anyway, thanks friends. But we've come out the other side knowing that the weak link in this very sorry chain of incredibly weak links, is this dependency. This commit removes this dependency for a better monster: the one we know. It is at this time that I'd like to announce that I am quitting my job at Pulumi. I thank you all for the good times, but mostly, for taking this code over for me.
2019-11-11 23:00:46 +00:00
});