summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/websockets/stream
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/websockets/stream')
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/README.md9
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/abort.any.js50
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/backpressure-receive.any.js40
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/backpressure-send.any.js25
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/close.any.js183
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/constructor.any.js71
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/resources/url-constants.js8
-rw-r--r--testing/web-platform/tests/websockets/stream/tentative/websocket-error.any.js50
8 files changed, 436 insertions, 0 deletions
diff --git a/testing/web-platform/tests/websockets/stream/tentative/README.md b/testing/web-platform/tests/websockets/stream/tentative/README.md
new file mode 100644
index 0000000000..6c51588774
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/README.md
@@ -0,0 +1,9 @@
+# WebSocketStream tentative tests
+
+Tests in this directory are for the proposed "WebSocketStream" interface to the
+WebSocket protocol. This is not yet standardised and browsers should not be
+expected to pass these tests.
+
+See the explainer at
+https://github.com/ricea/websocketstream-explainer/blob/master/README.md for
+more information about the API.
diff --git a/testing/web-platform/tests/websockets/stream/tentative/abort.any.js b/testing/web-platform/tests/websockets/stream/tentative/abort.any.js
new file mode 100644
index 0000000000..9047f246f9
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/abort.any.js
@@ -0,0 +1,50 @@
+// META: script=../../constants.sub.js
+// META: script=resources/url-constants.js
+// META: script=/common/utils.js
+// META: global=window,worker
+// META: variant=?wss
+// META: variant=?wpt_flags=h2
+
+promise_test(async t => {
+ const controller = new AbortController();
+ controller.abort();
+ const key = token();
+ const wsUrl = new URL(
+ `/fetch/api/resources/stash-put.py?key=${key}&value=connected`,
+ location.href);
+ wsUrl.protocol = wsUrl.protocol.replace('http', 'ws');
+ // We intentionally use the port for the HTTP server, not the WebSocket
+ // server, because we don't expect the connection to be performed.
+ const wss = new WebSocketStream(wsUrl, { signal: controller.signal });
+ await promise_rejects_dom(
+ t, 'AbortError', wss.opened, 'opened should reject');
+ await promise_rejects_dom(
+ t, 'AbortError', wss.closed, 'closed should reject');
+ // An incorrect implementation could pass this test due a race condition,
+ // but it is hard to completely eliminate the possibility.
+ const response = await fetch(`/fetch/api/resources/stash-take.py?key=${key}`);
+ assert_equals(await response.text(), 'null', 'response should be null');
+}, 'abort before constructing should prevent connection');
+
+promise_test(async t => {
+ const controller = new AbortController();
+ const wss = new WebSocketStream(`${BASEURL}/handshake_sleep_2`,
+ { signal: controller.signal });
+ // Give the connection a chance to start.
+ await new Promise(resolve => t.step_timeout(resolve, 0));
+ controller.abort();
+ await promise_rejects_dom(
+ t, 'AbortError', wss.opened, 'opened should reject');
+ await promise_rejects_dom(
+ t, 'AbortError', wss.closed, 'closed should reject');
+}, 'abort during handshake should work');
+
+promise_test(async t => {
+ const controller = new AbortController();
+ const wss = new WebSocketStream(ECHOURL, { signal: controller.signal });
+ const { readable, writable } = await wss.opened;
+ controller.abort();
+ writable.getWriter().write('connected');
+ const { value } = await readable.getReader().read();
+ assert_equals(value, 'connected', 'value should match');
+}, 'abort after connect should do nothing');
diff --git a/testing/web-platform/tests/websockets/stream/tentative/backpressure-receive.any.js b/testing/web-platform/tests/websockets/stream/tentative/backpressure-receive.any.js
new file mode 100644
index 0000000000..236bb2e40f
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/backpressure-receive.any.js
@@ -0,0 +1,40 @@
+// META: script=../../constants.sub.js
+// META: script=resources/url-constants.js
+// META: global=window,worker
+// META: timeout=long
+// META: variant=?wss
+// META: variant=?wpt_flags=h2
+
+// Allow for this much timer jitter.
+const JITTER_ALLOWANCE_MS = 200;
+const LARGE_MESSAGE_COUNT = 16;
+
+// This test works by using a server WebSocket handler which sends a large
+// message, and then sends a second message with the time it measured the first
+// message taking. On the browser side, we wait 2 seconds before reading from
+// the socket. This should ensure it takes at least 2 seconds to finish sending
+// the large message.
+promise_test(async t => {
+ const wss = new WebSocketStream(`${BASEURL}/send-backpressure`);
+ const { readable } = await wss.opened;
+ const reader = readable.getReader();
+
+ // Create backpressure for 2 seconds.
+ await new Promise(resolve => t.step_timeout(resolve, 2000));
+
+ // Skip the empty message used to fill the readable queue.
+ await reader.read();
+
+ // Skip the large messages.
+ for (let i = 0; i < LARGE_MESSAGE_COUNT; ++i) {
+ await reader.read();
+ }
+
+ // Read the time it took.
+ const { value, done } = await reader.read();
+
+ // A browser can pass this test simply by being slow. This may be a source of
+ // flakiness for browsers that do not implement backpressure properly.
+ assert_greater_than_equal(Number(value), 2 - JITTER_ALLOWANCE_MS / 1000,
+ 'data send should have taken at least 2 seconds');
+}, 'backpressure should be applied to received messages');
diff --git a/testing/web-platform/tests/websockets/stream/tentative/backpressure-send.any.js b/testing/web-platform/tests/websockets/stream/tentative/backpressure-send.any.js
new file mode 100644
index 0000000000..e4a80f6e1c
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/backpressure-send.any.js
@@ -0,0 +1,25 @@
+// META: script=../../constants.sub.js
+// META: script=resources/url-constants.js
+// META: global=window,worker
+// META: timeout=long
+// META: variant=?wss
+// META: variant=?wpt_flags=h2
+
+// Allow for this much timer jitter.
+const JITTER_ALLOWANCE_MS = 200;
+
+// The amount of buffering a WebSocket connection has is not standardised, but
+// it's reasonable to expect that it will not be as large as 8MB.
+const MESSAGE_SIZE = 8 * 1024 * 1024;
+
+// In this test, the server WebSocket handler waits 2 seconds, and the browser
+// times how long it takes to send the first message.
+promise_test(async t => {
+ const wss = new WebSocketStream(`${BASEURL}/receive-backpressure`);
+ const { writable } = await wss.opened;
+ const writer = writable.getWriter();
+ const start = performance.now();
+ await writer.write(new Uint8Array(MESSAGE_SIZE));
+ const elapsed = performance.now() - start;
+ assert_greater_than_equal(elapsed, 2000 - JITTER_ALLOWANCE_MS);
+}, 'backpressure should be applied to sent messages');
diff --git a/testing/web-platform/tests/websockets/stream/tentative/close.any.js b/testing/web-platform/tests/websockets/stream/tentative/close.any.js
new file mode 100644
index 0000000000..098caf31c8
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/close.any.js
@@ -0,0 +1,183 @@
+// META: script=../../constants.sub.js
+// META: script=resources/url-constants.js
+// META: global=window,worker
+// META: variant=?wss
+// META: variant=?wpt_flags=h2
+
+'use strict';
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ wss.close({ closeCode: 3456, reason: 'pizza' });
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 3456, 'code should match');
+ assert_equals(reason, 'pizza', 'reason should match');
+}, 'close code should be sent to server and reflected back');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ wss.close();
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1005, 'code should be unset');
+ assert_equals(reason, '', 'reason should be empty');
+}, 'no close argument should send empty Close frame');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ wss.close({});
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1005, 'code should be unset');
+ assert_equals(reason, '', 'reason should be empty');
+}, 'unspecified close code should send empty Close frame');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ wss.close({reason: ''});
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1005, 'code should be unset');
+ assert_equals(reason, '', 'reason should be empty');
+}, 'unspecified close code with empty reason should send empty Close frame');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ wss.close({reason: 'non-empty'});
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1000, 'code should be set');
+ assert_equals(reason, 'non-empty', 'reason should match');
+}, 'unspecified close code with non-empty reason should set code to 1000');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ assert_throws_js(TypeError, () => wss.close(true),
+ 'close should throw a TypeError');
+}, 'close(true) should throw a TypeError');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ const reason = '.'.repeat(124);
+ assert_throws_dom('SyntaxError', () => wss.close({ reason }),
+ 'close should throw a SyntaxError');
+}, 'close() with an overlong reason should throw');
+
+function IsWebSocketError(e) {
+ return e.constructor == WebSocketError;
+}
+
+promise_test(t => {
+ const wss = new WebSocketStream(ECHOURL);
+ wss.close();
+ return Promise.all([
+ wss.opened.then(t.unreached_func('should have rejected')).catch(e => assert_true(IsWebSocketError(e))),
+ wss.closed.then(t.unreached_func('should have rejected')).catch(e => assert_true(IsWebSocketError(e))),
+ ]);
+}, 'close during handshake should work');
+
+for (const invalidCode of [999, 1001, 2999, 5000]) {
+ promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ assert_throws_dom('InvalidAccessError', () => wss.close({ closeCode: invalidCode }),
+ 'close should throw an InvalidAccessError');
+ }, `close() with invalid code ${invalidCode} should throw`);
+}
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const { writable } = await wss.opened;
+ writable.getWriter().close();
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1005, 'code should be unset');
+ assert_equals(reason, '', 'reason should be empty');
+}, 'closing the writable should result in a clean close');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(`${BASEURL}/delayed-passive-close`);
+ const { writable } = await wss.opened;
+ const startTime = performance.now();
+ await writable.getWriter().close();
+ const elapsed = performance.now() - startTime;
+ const jitterAllowance = 100;
+ assert_greater_than_equal(elapsed, 1000 - jitterAllowance,
+ 'one second should have elapsed');
+}, 'writer close() promise should not resolve until handshake completes');
+
+const abortOrCancel = [
+ {
+ method: 'abort',
+ voweling: 'aborting',
+ stream: 'writable',
+ },
+ {
+ method: 'cancel',
+ voweling: 'canceling',
+ stream: 'readable',
+ },
+];
+
+for (const { method, voweling, stream } of abortOrCancel) {
+
+ promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const info = await wss.opened;
+ info[stream][method]();
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1005, 'code should be unset');
+ assert_equals(reason, '', 'reason should be empty');
+ }, `${voweling} the ${stream} should result in a clean close`);
+
+ promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const info = await wss.opened;
+ info[stream][method]({ closeCode: 3333, reason: 'obsolete' });
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1005, 'code should be unset');
+ assert_equals(reason, '', 'reason should be empty');
+ }, `${voweling} the ${stream} with attributes not wrapped in a WebSocketError should be ignored`);
+
+ promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const info = await wss.opened;
+ info[stream][method](new WebSocketError('', { closeCode: 3333 }));
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 3333, 'code should be used');
+ assert_equals(reason, '', 'reason should be empty');
+ }, `${voweling} the ${stream} with a code should send that code`);
+
+ promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const info = await wss.opened;
+ info[stream][method](new WebSocketError('', { closeCode: 3456, reason: 'set' }));
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 3456, 'code should be used');
+ assert_equals(reason, 'set', 'reason should be used');
+ }, `${voweling} the ${stream} with a code and reason should use them`);
+
+ promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const info = await wss.opened;
+ info[stream][method](new WebSocketError('', { reason: 'specified' }));
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1000, 'code should be defaulted');
+ assert_equals(reason, 'specified', 'reason should be used');
+ }, `${voweling} the ${stream} with a reason but no code should default the close code`);
+
+ promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const info = await wss.opened;
+ const domException = new DOMException('yes', 'DataCloneError');
+ domException.closeCode = 1000;
+ domException.reason = 'should be ignored';
+ info[stream][method](domException);
+ const { closeCode, reason } = await wss.closed;
+ assert_equals(closeCode, 1005, 'code should be unset');
+ assert_equals(reason, '', 'reason should be empty');
+ }, `${voweling} the ${stream} with a DOMException not set code or reason`);
+
+}
diff --git a/testing/web-platform/tests/websockets/stream/tentative/constructor.any.js b/testing/web-platform/tests/websockets/stream/tentative/constructor.any.js
new file mode 100644
index 0000000000..66df974303
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/constructor.any.js
@@ -0,0 +1,71 @@
+// META: script=../../constants.sub.js
+// META: script=resources/url-constants.js
+// META: global=window,worker
+// META: variant=?wss
+// META: variant=?wpt_flags=h2
+
+test(() => {
+ assert_throws_js(TypeError, () => new WebSocketStream(),
+ 'constructor should throw');
+}, 'constructing with no URL should throw');
+
+test(() => {
+ assert_throws_dom('SyntaxError', () => new WebSocketStream('invalid:'),
+ 'constructor should throw');
+}, 'constructing with an invalid URL should throw');
+
+test(() => {
+ assert_throws_js(TypeError,
+ () => new WebSocketStream(`${BASEURL}/`, true),
+ 'constructor should throw');
+}, 'constructing with invalid options should throw');
+
+test(() => {
+ assert_throws_js(TypeError,
+ () => new WebSocketStream(`${BASEURL}/`, {protocols: 'hi'}),
+ 'constructor should throw');
+}, 'protocols should be required to be a list');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ await wss.opened;
+ assert_equals(wss.url, ECHOURL, 'url should match');
+ wss.close();
+}, 'constructing with a valid URL should work');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(`${BASEURL}/protocol_array`,
+ {protocols: ['alpha', 'beta']});
+ const { readable, protocol } = await wss.opened;
+ assert_equals(protocol, 'alpha', 'protocol should be right');
+ const reader = readable.getReader();
+ const { value, done } = await reader.read();
+ assert_equals(value, 'alpha', 'message contents should match');
+ wss.close();
+}, 'setting a protocol in the constructor should work');
+
+function IsWebSocketError(e) {
+ return e.constructor == WebSocketError;
+}
+
+promise_test(t => {
+ const wss = new WebSocketStream(`${BASEURL}/404`);
+ return Promise.all([
+ wss.opened.then(t.unreached_func('should have rejected')).catch(e => assert_true(IsWebSocketError(e))),
+ wss.closed.then(t.unreached_func('should have rejected')).catch(e => assert_true(IsWebSocketError(e))),
+ ]);
+}, 'connection failure should reject the promises');
+
+promise_test(async () => {
+ const wss = new WebSocketStream(ECHOURL);
+ const { readable, writable, protocol, extensions} = await wss.opened;
+ // Verify that |readable| really is a ReadableStream using the getReader()
+ // brand check. If it doesn't throw the test passes.
+ ReadableStream.prototype.getReader.call(readable);
+ // Verify that |writable| really is a WritableStream using the getWriter()
+ // brand check. If it doesn't throw the test passes.
+ WritableStream.prototype.getWriter.call(writable);
+ assert_equals(typeof protocol, 'string', 'protocol should be a string');
+ assert_equals(typeof extensions, 'string', 'extensions should be a string');
+ wss.close();
+}, 'wss.opened should resolve to the right types');
diff --git a/testing/web-platform/tests/websockets/stream/tentative/resources/url-constants.js b/testing/web-platform/tests/websockets/stream/tentative/resources/url-constants.js
new file mode 100644
index 0000000000..fe681af5c4
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/resources/url-constants.js
@@ -0,0 +1,8 @@
+// The file including this must also include ../constants.sub.js to pick up the
+// necessary constants.
+
+const {BASEURL, ECHOURL} = (() => {
+ const BASEURL = SCHEME_DOMAIN_PORT;
+ const ECHOURL = `${BASEURL}/echo`;
+ return {BASEURL, ECHOURL};
+})();
diff --git a/testing/web-platform/tests/websockets/stream/tentative/websocket-error.any.js b/testing/web-platform/tests/websockets/stream/tentative/websocket-error.any.js
new file mode 100644
index 0000000000..b114bbb3e3
--- /dev/null
+++ b/testing/web-platform/tests/websockets/stream/tentative/websocket-error.any.js
@@ -0,0 +1,50 @@
+// META: global=window,worker
+
+'use strict';
+
+test(() => {
+ const error = new WebSocketError();
+ assert_equals(error.code, 0, 'DOMException code should be 0');
+ assert_equals(error.name, 'WebSocketError', 'name should be correct');
+ assert_equals(error.message, '', 'DOMException message should be empty');
+ assert_equals(error.closeCode, null, 'closeCode should be null');
+ assert_equals(error.reason, '', 'reason should be empty');
+}, 'WebSocketError defaults should be correct');
+
+test(() => {
+ const error = new WebSocketError('message', { closeCode: 3456, reason: 'reason' });
+ assert_equals(error.code, 0, 'DOMException code should be 0');
+ assert_equals(error.name, 'WebSocketError', 'name should be correct');
+ assert_equals(error.message, 'message', 'DOMException message should be set');
+ assert_equals(error.closeCode, 3456, 'closeCode should match');
+ assert_equals(error.reason, 'reason', 'reason should match');
+}, 'WebSocketError should be initialised from arguments');
+
+for (const invalidCode of [999, 1001, 2999, 5000]) {
+ test(() => {
+ assert_throws_dom('InvalidAccessError', () => new WebSocketError('', { closeCode: invalidCode }),
+ 'invalid code should throw an InvalidAccessError');
+ }, `new WebSocketError with invalid code ${invalidCode} should throw`);
+}
+
+test(() => {
+ const error = new WebSocketError('', { closeCode: 3333 });
+ assert_equals(error.closeCode, 3333, 'code should be used');
+ assert_equals(error.reason, '', 'reason should be empty');
+}, 'passing only close code to WebSocketError should work');
+
+test(() => {
+ const error = new WebSocketError('', { reason: 'specified' });
+ assert_equals(error.closeCode, 1000, 'code should be defaulted');
+ assert_equals(error.reason, 'specified', 'reason should be used');
+}, 'passing a non-empty reason should cause the close code to be set to 1000');
+
+test(() => {
+ assert_throws_dom('SyntaxError', () => new WebSocketError('', { closeCode: 1000, reason: 'x'.repeat(124) }),
+ 'overlong reason should trigger SyntaxError');
+}, 'overlong reason should throw');
+
+test(() => {
+ assert_throws_dom('SyntaxError', () => new WebSocketError('', { reason: '🔌'.repeat(32) }),
+ 'overlong reason should throw');
+}, 'reason should be rejected based on utf-8 bytes, not character count');