diff options
author | 2023-01-05 08:37:14 +0200 | |
---|---|---|
committer | 2023-01-04 22:37:14 -0800 | |
commit | 80736043d64941bafe8a9c42431ade4ad77d57e3 (patch) | |
tree | b4dbae90f6c720f1d530e3c53f95a8b620e60baf | |
parent | 3b259211df10e06ddf5844898a6ffa3c9679e4d8 (diff) | |
download | bun-80736043d64941bafe8a9c42431ade4ad77d57e3.tar.gz bun-80736043d64941bafe8a9c42431ade4ad77d57e3.tar.zst bun-80736043d64941bafe8a9c42431ade4ad77d57e3.zip |
implement `expect().toThrow()` (#1727)
- fix bugs in `JSBufferList`
- add tests
-rw-r--r-- | packages/bun-types/bun-test.d.ts | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/JSBufferList.cpp | 58 | ||||
-rw-r--r-- | src/bun.js/test/jest.zig | 82 | ||||
-rw-r--r-- | test/bun.js/bufferlist.test.ts | 187 | ||||
-rw-r--r-- | test/bun.js/websocket-server.test.ts | 172 |
5 files changed, 370 insertions, 130 deletions
diff --git a/packages/bun-types/bun-test.d.ts b/packages/bun-types/bun-test.d.ts index 7567c5ad2..1c14f8769 100644 --- a/packages/bun-types/bun-test.d.ts +++ b/packages/bun-types/bun-test.d.ts @@ -50,6 +50,7 @@ declare module "bun:test" { toBeGreaterThanOrEqual(value: number | bigint): void; toBeLessThan(value: number | bigint): void; toBeLessThanOrEqual(value: number | bigint): void; + toThrow(message: string | ErrorConstructor): void; } } diff --git a/src/bun.js/bindings/JSBufferList.cpp b/src/bun.js/bindings/JSBufferList.cpp index e16c8cf88..e54b433e5 100644 --- a/src/bun.js/bindings/JSBufferList.cpp +++ b/src/bun.js/bindings/JSBufferList.cpp @@ -2,7 +2,6 @@ #include "JSBuffer.h" #include "JavaScriptCore/Lookup.h" #include "JavaScriptCore/ObjectConstructor.h" -#include "JavaScriptCore/JSGenericTypedArrayViewInlines.h" #include "ZigGlobalObject.h" #include "JSDOMOperation.h" #include "headers.h" @@ -48,6 +47,9 @@ JSC::JSValue JSBufferList::concat(JSC::VM& vm, JSC::JSGlobalObject* lexicalGloba if (UNLIKELY(!array)) { return throwTypeError(lexicalGlobalObject, throwScope, "concat can only be called when all buffers are Uint8Array"_s); } + if (UNLIKELY(array->byteLength() > n)) { + return throwRangeError(lexicalGlobalObject, throwScope, "specified size too small to fit all buffers"_s); + } RELEASE_AND_RETURN(throwScope, array); } // Buffer.allocUnsafe(n >>> 0) @@ -106,10 +108,10 @@ JSC::JSValue JSBufferList::consume(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlob return _getBuffer(vm, lexicalGlobalObject, n); } -JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t n) +JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t total) { auto throwScope = DECLARE_THROW_SCOPE(vm); - if (n == 0 || length() == 0) { + if (total <= 0 || length() == 0) { RELEASE_AND_RETURN(throwScope, JSC::jsEmptyString(vm)); } @@ -118,7 +120,8 @@ JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalG if (UNLIKELY(!str)) { return throwTypeError(lexicalGlobalObject, throwScope, "_getString can only be called when all buffers are string"_s); } - size_t len = str->length(); + const size_t len = str->length(); + size_t n = total; if (n == len) { m_deque.removeFirst(); @@ -131,32 +134,33 @@ JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalG } JSRopeString::RopeBuilder<RecordOverflow> ropeBuilder(vm); - for (const auto end = m_deque.end(); iter != end && n > 0; ++iter) { + for (const auto end = m_deque.end(); iter != end; ++iter) { JSC::JSString* str = JSC::jsDynamicCast<JSC::JSString*>(iter->get()); if (UNLIKELY(!str)) { return throwTypeError(lexicalGlobalObject, throwScope, "_getString can only be called when all buffers are string"_s); } - size_t len = str->length(); + const size_t len = str->length(); if (n < len) { JSString* firstHalf = JSC::jsSubstring(lexicalGlobalObject, str, 0, n); if (!ropeBuilder.append(firstHalf)) return throwOutOfMemoryError(lexicalGlobalObject, throwScope); iter->set(vm, this, JSC::jsSubstring(lexicalGlobalObject, str, n, len - n)); - } else { - if (!ropeBuilder.append(str)) - return throwOutOfMemoryError(lexicalGlobalObject, throwScope); - m_deque.removeFirst(); + break; } - n -= static_cast<int32_t>(len); + if (!ropeBuilder.append(str)) + return throwOutOfMemoryError(lexicalGlobalObject, throwScope); + m_deque.removeFirst(); + if (n == len) break; + n -= len; } RELEASE_AND_RETURN(throwScope, ropeBuilder.release()); } -JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t n) +JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t total) { auto throwScope = DECLARE_THROW_SCOPE(vm); auto* subclassStructure = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject)->JSBufferSubclassStructure(); - if (n == 0 || length() == 0) { + if (total <= 0 || length() == 0) { // Buffer.alloc(0) RELEASE_AND_RETURN(throwScope, JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, 0)); } @@ -166,13 +170,15 @@ JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalG if (UNLIKELY(!array)) { return throwTypeError(lexicalGlobalObject, throwScope, "_getBuffer can only be called when all buffers are Uint8Array"_s); } - size_t len = array->byteLength(); + const size_t len = array->byteLength(); + size_t n = total; if (n == len) { + m_deque.removeFirst(); RELEASE_AND_RETURN(throwScope, array); } if (n < len) { - auto buffer = array->existingBuffer(); + auto buffer = array->possiblySharedBuffer(); JSC::JSUint8Array* retArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, 0, n); JSC::JSUint8Array* newArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, n, len - n); iter->set(vm, this, newArray); @@ -182,29 +188,31 @@ JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalG // Buffer.allocUnsafe(n >>> 0) auto arrayBuffer = JSC::ArrayBuffer::tryCreateUninitialized(n, 1); if (UNLIKELY(!arrayBuffer)) { - return throwTypeError(lexicalGlobalObject, throwScope); + return throwOutOfMemoryError(lexicalGlobalObject, throwScope); } JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(arrayBuffer), 0, n); size_t offset = 0; - for (const auto end = m_deque.end(); iter != end && n > 0; ++iter) { + for (const auto end = m_deque.end(); iter != end; ++iter) { JSC::JSUint8Array* array = JSC::jsDynamicCast<JSC::JSUint8Array*>(iter->get()); if (UNLIKELY(!array)) { return throwTypeError(lexicalGlobalObject, throwScope, "_getBuffer can only be called when all buffers are Uint8Array"_s); } - size_t len = array->byteLength(); + const size_t len = array->byteLength(); if (n < len) { if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, 0, n, JSC::CopyType::Unobservable))) { return throwOutOfMemoryError(lexicalGlobalObject, throwScope); } - JSC::JSUint8Array* newArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, array->existingBuffer(), n, len - n); + auto buffer = array->possiblySharedBuffer(); + JSC::JSUint8Array* newArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, n, len - n); iter->set(vm, this, newArray); - } else { - if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, 0, len, JSC::CopyType::Unobservable))) { - return throwOutOfMemoryError(lexicalGlobalObject, throwScope); - } - m_deque.removeFirst(); + break; + } + if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, 0, len, JSC::CopyType::Unobservable))) { + return throwOutOfMemoryError(lexicalGlobalObject, throwScope); } - n -= static_cast<int32_t>(len); + m_deque.removeFirst(); + if (n == len) break; + n -= len; offset += len; } RELEASE_AND_RETURN(throwScope, uint8Array); diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index 24c1bd0e1..ba9234c6d 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -535,7 +535,7 @@ pub const Expect = struct { defer this.postMatch(globalObject); const thisValue = callFrame.this(); const value: JSValue = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Interal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -564,7 +564,7 @@ pub const Expect = struct { const thisValue = callFrame.this(); const value: JSValue = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Interal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -596,7 +596,7 @@ pub const Expect = struct { const thisValue = callFrame.this(); const value: JSValue = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Interal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -623,7 +623,7 @@ pub const Expect = struct { const thisValue = callFrame.this(); const value: JSValue = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Interal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -851,7 +851,7 @@ pub const Expect = struct { other_value.ensureStillAlive(); const value = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Internal consistency error: thie expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -914,7 +914,7 @@ pub const Expect = struct { other_value.ensureStillAlive(); const value = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Internal consistency error: thie expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -977,7 +977,7 @@ pub const Expect = struct { other_value.ensureStillAlive(); const value = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Internal consistency error: thie expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -1040,7 +1040,7 @@ pub const Expect = struct { other_value.ensureStillAlive(); const value = Expect.capturedValueGetCached(thisValue) orelse { - globalObject.throw("Internal consistency error: thie expect(value) was garbage collected but it should not have been!", .{}); + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); return .zero; }; value.ensureStillAlive(); @@ -1080,6 +1080,71 @@ pub const Expect = struct { return .zero; } + pub fn toThrow(this: *Expect, globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSValue { + defer this.postMatch(globalObject); + + const thisValue = callFrame.this(); + const _arguments = callFrame.arguments(1); + const arguments: []const JSValue = _arguments.ptr[0.._arguments.len]; + + if (arguments.len < 1) { + globalObject.throwInvalidArguments("toThrow() requires 1 argument", .{}); + return .zero; + } + + if (this.scope.tests.items.len <= this.test_id) { + globalObject.throw("toThrow() must be called in a test", .{}); + return .zero; + } + + active_test_expectation_counter.actual += 1; + + const expected = arguments[0]; + expected.ensureStillAlive(); + + const value = Expect.capturedValueGetCached(thisValue) orelse { + globalObject.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{}); + return .zero; + }; + value.ensureStillAlive(); + + if ((!value.jsType().isFunction())) { + globalObject.throw("Expected value must be a function", .{}); + return .zero; + } + + const not = this.op.contains(.not); + const result = value.call(globalObject, &.{}).toError(globalObject); + if (result.isUndefined()) { + if (not) return thisValue; + globalObject.throw("Expected function to throw", .{}); + return .zero; + } else if(not) { + globalObject.throw("Expected function not to throw", .{}); + return .zero; + } + + if (expected.isString()) { + const message = result.get(globalObject, "message"); + // TODO support partial match + const pass = if (message) |actual| expected.isSameValue(actual, globalObject) else false; + + if (pass) return thisValue; + globalObject.throw("\n\tExpected: {s}\n\tReceived: {s}", .{ + expected.getZigString(globalObject), + if (message) |actual| actual.getZigString(globalObject) else ZigString.init("undefined"), + }); + return .zero; + } + + if (result.isInstanceOf(globalObject, expected)) return thisValue; + globalObject.throw("\n\tExpected type: {s}\n\tReceived type: {s}", .{ + expected.getName(globalObject), + if (result.get(globalObject, "name")) |name| name.getZigString(globalObject) else ZigString.init("<UnknownError>"), + }); + return .zero; + } + pub const toHaveBeenCalledTimes = notImplementedJSCFn; pub const toHaveBeenCalledWith = notImplementedJSCFn; pub const toHaveBeenLastCalledWith = notImplementedJSCFn; @@ -1095,7 +1160,6 @@ pub const Expect = struct { pub const toMatchObject = notImplementedJSCFn; pub const toMatchSnapshot = notImplementedJSCFn; pub const toMatchInlineSnapshot = notImplementedJSCFn; - pub const toThrow = notImplementedJSCFn; pub const toThrowErrorMatchingSnapshot = notImplementedJSCFn; pub const toThrowErrorMatchingInlineSnapshot = notImplementedJSCFn; diff --git a/test/bun.js/bufferlist.test.ts b/test/bun.js/bufferlist.test.ts new file mode 100644 index 000000000..e344d49b4 --- /dev/null +++ b/test/bun.js/bufferlist.test.ts @@ -0,0 +1,187 @@ +import { Readable } from "stream"; +import { it, expect } from "bun:test"; + +function makeUint8Array(str) { + return new Uint8Array([].map.call(str, function(ch) { + return ch.charCodeAt(0); + })); +} + +it("should work with .clear()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push({})).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.push({})).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.clear()).toBeUndefined(); + expect(list.length).toBe(0); +}); + +it("should work with .concat()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("foo"))).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.concat(3)).toEqual(new Uint8Array([ 102, 111, 111 ])); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.concat(10)).toEqual(new Uint8Array([ + 102, 111, 111, 98, 97, + 114, 0, 0, 0, 0, + ])); +}); + +it("should fail on .concat() with invalid items", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push("foo")).toBeUndefined(); + expect(() => { + list.concat(42); + }).toThrow(TypeError); +}); + +it("should fail on .concat() buffer overflow", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("foo"))).toBeUndefined(); + expect(list.length).toBe(1); + expect(() => { + list.concat(2); + }).toThrow(RangeError); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(2); + expect(() => { + list.concat(5); + }).toThrow(RangeError); +}); + +it("should work with .consume() on strings", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.consume(42, true)).toBe(""); + expect(list.push("foo")).toBeUndefined(); + expect(list.push("bar")).toBeUndefined(); + expect(list.push("baz")).toBeUndefined(); + expect(list.push("moo")).toBeUndefined(); + expect(list.push("moz")).toBeUndefined(); + expect(list.length).toBe(5); + expect(list.consume(3, true)).toBe("foo"); + expect(list.length).toBe(4); + expect(list.consume(4, true)).toBe("barb"); + expect(list.length).toBe(3); + expect(list.consume(256, true)).toBe("azmoomoz"); + expect(list.length).toBe(0); +}); + +it("should work with .consume() on buffers", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.consume(42, false)).toEqual(new Uint8Array()); + expect(list.push(makeUint8Array("foo"))).toBeUndefined(); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.push(makeUint8Array("baz"))).toBeUndefined(); + expect(list.push(makeUint8Array("moo"))).toBeUndefined(); + expect(list.push(makeUint8Array("moz"))).toBeUndefined(); + expect(list.length).toBe(5); + expect(list.consume(3, false)).toEqual(makeUint8Array("foo")); + expect(list.length).toBe(4); + expect(list.consume(2, false)).toEqual(makeUint8Array("ba")); + expect(list.length).toBe(4); + expect(list.consume(4, false)).toEqual(makeUint8Array("rbaz")); + expect(list.length).toBe(2); + expect(list.consume(10, false)).toEqual(new Uint8Array([ + 109, 111, 111, 109, 111, + 122, 0, 0, 0, 0, + ])); + expect(list.length).toBe(0); +}); + +it("should fail on .consume() with invalid items", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push("foo")).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.consume(0, false)).toEqual(new Uint8Array([])); + expect(() => { + list.consume(1, false); + }).toThrow(TypeError); + expect(list.consume(3, true)).toBe("foo"); + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.consume(0, true)).toEqual(""); + expect(() => { + list.consume(1, true); + }).toThrow(TypeError); + expect(list.consume(3, false)).toEqual(new Uint8Array([ 98, 97, 114 ])); +}); + +it("should work with .first()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.first()).toBeUndefined(); + const item = {}; + expect(list.push(item)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.first()).toBe(item); +}); + +it("should work with .join()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(42)).toBeUndefined(); + expect(list.push(null)).toBeUndefined(); + expect(list.push("foo")).toBeUndefined(); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(4); + expect(list.join("")).toBe("42nullfoo98,97,114"); + expect(list.join(",")).toBe("42,null,foo,98,97,114"); + expect(list.join(" baz ")).toBe("42 baz null baz foo baz 98,97,114"); +}); + +it("should work with .push()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + const item1 = {}; + expect(list.push(item1)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.first()).toBe(item1); + const item2 = {}; + expect(list.push(item2)).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.shift()).toBe(item1); + expect(list.shift()).toBe(item2); + expect(list.shift()).toBeUndefined(); +}); + +it("should work with .shift()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.shift()).toBeUndefined(); + const item = {}; + expect(list.push(item)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.shift()).toBe(item); + expect(list.shift()).toBeUndefined(); +}); + +it("should work with .unshift()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + const item1 = {}; + expect(list.unshift(item1)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.first()).toBe(item1); + const item2 = {}; + expect(list.push(item2)).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.first()).toBe(item1); + const item3 = {}; + expect(list.unshift(item3)).toBeUndefined(); + expect(list.length).toBe(3); + expect(list.shift()).toBe(item3); + expect(list.shift()).toBe(item1); + expect(list.shift()).toBe(item2); + expect(list.shift()).toBeUndefined(); +}); diff --git a/test/bun.js/websocket-server.test.ts b/test/bun.js/websocket-server.test.ts index 949b1fc6e..1d9c15341 100644 --- a/test/bun.js/websocket-server.test.ts +++ b/test/bun.js/websocket-server.test.ts @@ -104,7 +104,6 @@ describe("websocket server", () => { }); second.close(); - } catch (r) { } finally { server.stop(); } @@ -157,59 +156,58 @@ describe("websocket server", () => { }); it("headers error doesn't crash", async () => { - var resolve, reject; - var server = serve({ - port: getPort(), - websocket: { - open(ws) {}, - message(ws, msg) {}, - close() { + await new Promise<void>((resolve, reject) => { + const server = serve({ + port: getPort(), + websocket: { + open(ws) { + server.stop(); + }, + message(ws, msg) {}, + close() { + resolve(); + }, + }, + error(err) { resolve(); }, - }, - error(err) { - resolve(); - }, - fetch(req, server) { - try { - if ( - server.upgrade(req, { - data: "hello world", - headers: 1238 as any, - }) - ) { - reject(new Error("should not upgrade")); - return; - } - } catch (e) { + fetch(req, server) { + server.stop(); + expect(() => { + if ( + server.upgrade(req, { + data: "hello world", + headers: 1238 as any, + }) + ) { + reject(new Error("should not upgrade")); + return new Response("should not upgrade"); + } + }).toThrow("upgrade options.headers must be a Headers or an object"); resolve(); return new Response("success"); - } - - reject(new Error("should throw error")); - return new Response("noooooo hello world"); - }, - }); + }, + }); - await new Promise<void>((resolve_, reject) => { - resolve = resolve_; const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); websocket.onopen = () => websocket.close(); websocket.onmessage = (e) => {}; websocket.onerror = (e) => {}; }); - server.stop(); }); it("can do hello world", async () => { - var server = serve({ + const server = serve({ port: getPort(), websocket: { - open(ws) {}, + open(ws) { + server.stop(); + }, message(ws, msg) { ws.send("hello world"); }, }, fetch(req, server) { + server.stop(); if ( server.upgrade(req, { data: "hello world", @@ -241,9 +239,7 @@ describe("websocket server", () => { resolve(); } catch (r) { reject(r); - return; } finally { - server?.stop(); websocket.close(); } }; @@ -251,14 +247,15 @@ describe("websocket server", () => { reject(e); }; }); - server.stop(); }); it("fetch() allows a Response object to be returned for an upgraded ServerWebSocket", () => { - var server = serve({ + const server = serve({ port: getPort(), websocket: { - open(ws) {}, + open(ws) { + server.stop(); + }, message(ws, msg) { ws.send("hello world"); }, @@ -267,6 +264,7 @@ describe("websocket server", () => { console.error(err); }, fetch(req, server) { + server.stop(); if ( server.upgrade(req, { data: "hello world", @@ -300,9 +298,7 @@ describe("websocket server", () => { resolve(); } catch (r) { reject(r); - return; } finally { - server?.stop(); websocket.close(); } }; @@ -313,10 +309,12 @@ describe("websocket server", () => { }); it("fetch() allows a Promise<Response> object to be returned for an upgraded ServerWebSocket", () => { - var server = serve({ + const server = serve({ port: getPort(), websocket: { - async open(ws) {}, + async open(ws) { + server.stop(); + }, async message(ws, msg) { await 1; ws.send("hello world"); @@ -326,6 +324,7 @@ describe("websocket server", () => { console.error(err); }, async fetch(req, server) { + server.stop(); await 1; if ( server.upgrade(req, { @@ -359,9 +358,7 @@ describe("websocket server", () => { resolve(); } catch (r) { reject(r); - return; } finally { - server?.stop(); websocket.close(); } }; @@ -372,10 +369,12 @@ describe("websocket server", () => { }); it("binaryType works", async () => { var done = false; - var server = serve({ + const server = serve({ port: getPort(), websocket: { - open(ws) {}, + open(ws) { + server.stop(); + }, message(ws, msg) { if (ws.binaryType === "uint8array") { expect(ws.binaryType).toBe("uint8array"); @@ -392,6 +391,7 @@ describe("websocket server", () => { }, }, fetch(req, server) { + server.stop(); if (server.upgrade(req, { data: "hello world" })) { if (server.upgrade(req)) { throw new Error("should not upgrade twice"); @@ -414,24 +414,19 @@ describe("websocket server", () => { expect(e.data).toBe("hello world"); if (counter++ > 0) { - server?.stop(); websocket.close(); resolve(done); } websocket.send(Buffer.from("oaksd")); } catch (r) { - server?.stop(); websocket.close(); reject(r); - return; - } finally { } }; websocket.onerror = (e) => { reject(e); }; }); - server.stop(); }); it("does not upgrade for non-websocket connections", async () => { @@ -462,38 +457,35 @@ describe("websocket server", () => { it("does not upgrade for non-websocket servers", async () => { await new Promise<void>(async (resolve, reject) => { - var server = serve({ + const server = serve({ port: getPort(), - fetch(req, server) { - try { + server.stop(); + expect(() => { server.upgrade(req); - reject(new Error("should not upgrade")); - } catch (e) { - return new Response("success"); - } - - return new Response("fail"); + }).toThrow('To enable websocket support, set the "websocket" object in Bun.serve({})'); + return new Response("success"); }, }); const response = await fetch(`http://${server.hostname}:${server.port}`); expect(await response.text()).toBe("success"); resolve(); - server.stop(); }); }); it("async can do hello world", async () => { - var server = serve({ + const server = serve({ port: getPort(), websocket: { async open(ws) { + server.stop(); ws.send("hello world"); }, message(ws, msg) {}, }, async fetch(req, server) { + server.stop(); await 1; if (server.upgrade(req)) return; @@ -510,9 +502,7 @@ describe("websocket server", () => { resolve(); } catch (r) { reject(r); - return; } finally { - server?.stop(); websocket.close(); } }; @@ -633,10 +623,11 @@ describe("websocket server", () => { }); it("can do hello world corked", async () => { - var server = serve({ + const server = serve({ port: getPort(), websocket: { open(ws) { + server.stop(); ws.send("hello world"); }, message(ws, msg) { @@ -646,8 +637,8 @@ describe("websocket server", () => { }, }, fetch(req, server) { + server.stop(); if (server.upgrade(req)) return; - return new Response("noooooo hello world"); }, }); @@ -661,9 +652,7 @@ describe("websocket server", () => { resolve(); } catch (r) { reject(r); - return; } finally { - server?.stop(); websocket.close(); } }; @@ -675,10 +664,12 @@ describe("websocket server", () => { it("can do some back and forth", async () => { var dataCount = 0; - var server = serve({ + const server = serve({ port: getPort(), websocket: { - open(ws) {}, + open(ws) { + server.stop(); + }, message(ws, msg) { if (msg === "first") { ws.send("first"); @@ -688,6 +679,7 @@ describe("websocket server", () => { }, }, fetch(req, server) { + server.stop(); if ( server.upgrade(req, { data: { count: 0 }, @@ -731,12 +723,7 @@ describe("websocket server", () => { } } catch (r) { reject(r); - console.error(r); - server?.stop(); - console.log("i am closing!"); websocket.close(); - return; - } finally { } }; }); @@ -752,16 +739,19 @@ describe("websocket server", () => { var serverCounter = 0; var clientCounter = 0; - var server = serve({ + const server = serve({ port: getPort(), websocket: { - open(ws) {}, + open(ws) { + server.stop(); + }, message(ws, msg) { ws.send(sendQueue[serverCounter++] + " "); gcTick(); }, }, fetch(req, server) { + server.stop(); if ( server.upgrade(req, { data: { count: 0 }, @@ -793,15 +783,10 @@ describe("websocket server", () => { } catch (r) { reject(r); console.error(r); - server?.stop(); websocket.close(); - return; - } finally { } }; }); - - server?.stop(); }); // this test sends 100 messages to 10 connected clients via pubsub @@ -814,10 +799,11 @@ describe("websocket server", () => { } var serverCounter = 0; var clientCount = 0; - var server = serve({ + const server = serve({ port: getPort(), websocket: { open(ws) { + server.stop(); ws.subscribe("test"); gcTick(); if (!ws.isSubscribed("test")) { @@ -837,14 +823,12 @@ describe("websocket server", () => { ws.publish("test", sendQueue[serverCounter++] + " "); }, }, - fetch(req, server) { + fetch(req) { gcTick(); - - if ( - server.upgrade(req, { - data: { count: 0 }, - }) - ) + server.stop(); + if (server.upgrade(req, { + data: { count: 0 }, + })) return; return new Response("noooooo hello world"); }, @@ -922,17 +906,13 @@ describe("websocket server", () => { } } catch (r) { console.error(r); - server?.stop(); websocket.close(); rejectConnection(r); gcTick(); - return; - } finally { } }; } }); - server?.stop(); expect(serverCounter).toBe(sendQueue.length); }); }); |