diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/serial | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/serial')
45 files changed, 2285 insertions, 0 deletions
diff --git a/testing/web-platform/tests/serial/META.yml b/testing/web-platform/tests/serial/META.yml new file mode 100644 index 0000000000..64938c9298 --- /dev/null +++ b/testing/web-platform/tests/serial/META.yml @@ -0,0 +1 @@ +spec: https://wicg.github.io/serial/ diff --git a/testing/web-platform/tests/serial/README.md b/testing/web-platform/tests/serial/README.md new file mode 100644 index 0000000000..7040cf0a9d --- /dev/null +++ b/testing/web-platform/tests/serial/README.md @@ -0,0 +1,20 @@ +# Web Serial Testing + +Automated testing for the [Web Serial API] relies on a test-only interface which +must be provided by browsers under test. This is similar to [WebUSB] however +there is no separate specification of the API other than the tests themselves +and the Chromium implementation. + +Tests in this suite include `resources/automation.js` to detect and load the +test API as needed. + +The Chromium implementation is provided by +`../resources/chromium/fake-serial.js` using [MojoJS]. + +Tests with the "-manual" suffix do not use the test-only interface and expect a +real hardware device to be connected. The specific characteristics of the device +are described in each test. + +[MojoJS]: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/testing/web_platform_tests.md#mojojs +[WebUSB]: ../webusb +[Web Serial API]: https://wicg.github.io/serial diff --git a/testing/web-platform/tests/serial/idlharness.https.any.js b/testing/web-platform/tests/serial/idlharness.https.any.js new file mode 100644 index 0000000000..b240da933a --- /dev/null +++ b/testing/web-platform/tests/serial/idlharness.https.any.js @@ -0,0 +1,22 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +'use strict'; + +idl_test( + ['serial'], + ['html', 'dom'], + idl_array => { + idl_array.add_objects({ + Serial: ['navigator.serial'], + // TODO: SerialPort + // TODO: SerialPortInfo + }); + + if (self.GLOBAL.isWorker()) { + idl_array.add_objects({ WorkerNavigator: ['navigator'] }); + } else { + idl_array.add_objects({ Navigator: ['navigator'] }); + } + } +); diff --git a/testing/web-platform/tests/serial/resources/automation.js b/testing/web-platform/tests/serial/resources/automation.js new file mode 100644 index 0000000000..e88fdb1a9d --- /dev/null +++ b/testing/web-platform/tests/serial/resources/automation.js @@ -0,0 +1,66 @@ +'use strict'; + +// These tests rely on the User Agent providing an implementation of the +// FakeSerialService interface which replaces the platform-specific +// implementation of the Web Serial API with one that can be automated from +// Javascript for testing purposes. +// +// In Chromium-based browsers this implementation is provided by a polyfill +// in order to reduce the amount of test-only code shipped to users. To enable +// these tests the browser must be run with these options: +// +// --enable-blink-features=MojoJS,MojoJSTest + +let fakeSerialService = undefined; + +// Returns a SerialPort instance and associated FakeSerialPort instance. +async function getFakeSerialPort(fake) { + let token = fake.addPort(); + let fakePort = fake.getFakePort(token); + + let ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 1); + + let port = ports[0]; + assert_true(port instanceof SerialPort); + + return { port, fakePort }; +} + +function serial_test(func, name, properties) { + promise_test(async (test) => { + assert_implements(navigator.serial, 'missing navigator.serial'); + if (fakeSerialService === undefined) { + // Try loading a polyfill for the fake serial service. + if (isChromiumBased) { + const fakes = await import('/resources/chromium/fake-serial.js'); + fakeSerialService = fakes.fakeSerialService; + } + } + assert_implements(fakeSerialService, 'missing fakeSerialService after initialization'); + + fakeSerialService.start(); + try { + await func(test, fakeSerialService); + } finally { + fakeSerialService.stop(); + fakeSerialService.reset(); + } + }, name, properties); +} + +function trustedClick() { + return new Promise(resolve => { + let button = document.createElement('button'); + button.textContent = 'click to continue test'; + button.style.display = 'block'; + button.style.fontSize = '20px'; + button.style.padding = '10px'; + button.onclick = () => { + document.body.removeChild(button); + resolve(); + }; + document.body.appendChild(button); + test_driver.click(button); + }); +} diff --git a/testing/web-platform/tests/serial/resources/common.js b/testing/web-platform/tests/serial/resources/common.js new file mode 100644 index 0000000000..5177f83a86 --- /dev/null +++ b/testing/web-platform/tests/serial/resources/common.js @@ -0,0 +1,33 @@ +// Compare two Uint8Arrays. +function compareArrays(actual, expected) { + assert_true(actual instanceof Uint8Array, 'actual is Uint8Array'); + assert_true(expected instanceof Uint8Array, 'expected is Uint8Array'); + assert_equals(actual.byteLength, expected.byteLength, 'lengths equal'); + for (let i = 0; i < expected.byteLength; ++i) + assert_equals(actual[i], expected[i], `Mismatch at position ${i}.`); +} + +// Reads from |reader| until at least |targetLength| is read or the stream is +// closed. The data is returned as a combined Uint8Array. +async function readWithLength(reader, targetLength) { + const chunks = []; + let actualLength = 0; + + while (true) { + let {value, done} = await reader.read(); + chunks.push(value); + actualLength += value.byteLength; + + if (actualLength >= targetLength || done) { + // It would be better to allocate |buffer| up front with the number of + // of bytes expected but this is the best that can be done without a + // BYOB reader to control the amount of data read. + const buffer = new Uint8Array(actualLength); + chunks.reduce((offset, chunk) => { + buffer.set(chunk, offset); + return offset + chunk.byteLength; + }, 0); + return buffer; + } + } +} diff --git a/testing/web-platform/tests/serial/resources/manual.js b/testing/web-platform/tests/serial/resources/manual.js new file mode 100644 index 0000000000..ac19136b35 --- /dev/null +++ b/testing/web-platform/tests/serial/resources/manual.js @@ -0,0 +1,38 @@ +let manualTestPort = null; + +navigator.serial.addEventListener('disconnect', (e) => { + if (e.target === manualTestPort) { + manualTestPort = null; + } +}) + +async function getPortForManualTest() { + if (manualTestPort) { + return manualTestPort; + } + + const button = document.createElement('button'); + button.textContent = 'Click to select a device'; + button.style.display = 'block'; + button.style.fontSize = '20px'; + button.style.padding = '10px'; + + await new Promise((resolve) => { + button.onclick = () => { + document.body.removeChild(button); + resolve(); + }; + document.body.appendChild(button); + }); + + manualTestPort = await navigator.serial.requestPort({filters: []}); + assert_true(manualTestPort instanceof SerialPort); + + return manualTestPort; +} + +function manual_serial_test(func, name, properties) { + promise_test(async (test) => { + await func(test, await getPortForManualTest()); + }, name, properties); +} diff --git a/testing/web-platform/tests/serial/resources/serial-allowed-by-permissions-policy-worker.js b/testing/web-platform/tests/serial/resources/serial-allowed-by-permissions-policy-worker.js new file mode 100644 index 0000000000..cef0aacdfc --- /dev/null +++ b/testing/web-platform/tests/serial/resources/serial-allowed-by-permissions-policy-worker.js @@ -0,0 +1,14 @@ +'use strict'; + +importScripts('/resources/testharness.js'); + +let workerType; + +if (typeof postMessage === 'function') { + workerType = 'dedicated'; +} + +promise_test(() => navigator.serial.getPorts(), + `Inherited header permissions policy allows ${workerType} workers.`); + +done(); diff --git a/testing/web-platform/tests/serial/resources/serial-disabled-by-permissions-policy-worker.js b/testing/web-platform/tests/serial/resources/serial-disabled-by-permissions-policy-worker.js new file mode 100644 index 0000000000..afac442974 --- /dev/null +++ b/testing/web-platform/tests/serial/resources/serial-disabled-by-permissions-policy-worker.js @@ -0,0 +1,17 @@ +'use strict'; + +importScripts('/resources/testharness.js'); + +const header = 'Permissions-Policy header serial=()'; +let workerType; + +if (typeof postMessage === 'function') { + workerType = 'dedicated'; +} + +promise_test(() => navigator.serial.getPorts().then( + () => assert_unreached('expected promise to reject with SecurityError'), + error => assert_equals(error.name, 'SecurityError')), + `Inherited ${header} disallows ${workerType} workers.`); + +done(); diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html new file mode 100644 index 0000000000..ac278ff0cf --- /dev/null +++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<body> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/permissions-policy/resources/permissions-policy.js></script> +<script> +'use strict'; +const relative_path = '/permissions-policy/resources/permissions-policy-serial.html'; +const base_src = '/permissions-policy/resources/redirect-on-load.html#'; +const relative_worker_frame_path = + '/permissions-policy/resources/permissions-policy-serial-worker.html'; +const sub = 'https://{{domains[www]}}:{{ports[https][0]}}'; +const same_origin_src = base_src + relative_path; +const cross_origin_src = base_src + sub + relative_path; +const same_origin_worker_frame_src = base_src + relative_worker_frame_path; +const cross_origin_worker_frame_src = base_src + sub + + relative_worker_frame_path; +const header = 'Permissions-Policy allow="serial"'; + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, same_origin_src, + expect_feature_available_default, 'serial'); +}, header + ' allows same-origin relocation.'); + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, same_origin_worker_frame_src, + expect_feature_available_default, 'serial'); +}, header + ' allows workers in same-origin relocation.'); + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, cross_origin_src, + expect_feature_unavailable_default, 'serial'); +}, header + ' disallows cross-origin relocation.'); + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, cross_origin_worker_frame_src, + expect_feature_unavailable_default, 'serial'); +}, header + ' disallows workers in cross-origin relocation.'); +</script> +</body> diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute.https.sub.html b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute.https.sub.html new file mode 100644 index 0000000000..02560b180f --- /dev/null +++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy-attribute.https.sub.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<body> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/permissions-policy/resources/permissions-policy.js></script> +<script> +'use strict'; +const sub = 'https://{{domains[www]}}:{{ports[https][0]}}'; +const same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html'; +const cross_origin_src = sub + same_origin_src; +const same_origin_worker_frame_src = + '/permissions-policy/resources/permissions-policy-serial-worker.html'; +const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src; +const feature_name = 'Permissions policy "serial"'; +const header = 'allow="serial" attribute'; + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, same_origin_src, + expect_feature_available_default, 'serial'); +}, feature_name + ' can be enabled in same-origin iframe using ' + header); + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, same_origin_worker_frame_src, + expect_feature_available_default, 'serial'); +}, feature_name + ' can be enabled in a worker in same-origin iframe using ' + + header); + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, cross_origin_src, + expect_feature_available_default, 'serial'); +}, feature_name + ' can be enabled in cross-origin iframe using ' + header); + +async_test(t => { + test_feature_availability( + 'serial.getPorts()', t, cross_origin_worker_frame_src, + expect_feature_available_default, 'serial'); +}, feature_name + ' can be enabled in a worker in cross-origin iframe using ' + + header); + +fetch_tests_from_worker(new Worker( + 'resources/serial-allowed-by-permissions-policy-worker.js')); +</script> +</body> diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html new file mode 100644 index 0000000000..1be8eb979f --- /dev/null +++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<meta name=timeout content=long> +<body> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/permissions-policy/resources/permissions-policy.js></script> +<script> +'use strict'; +const sub = 'https://{{domains[www]}}:{{ports[https][0]}}'; +const same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html'; +const cross_origin_src = sub + same_origin_src; +const same_origin_worker_frame_src = + '/permissions-policy/resources/permissions-policy-serial-worker.html'; +const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src; +const header = 'Permissions-Policy header serial=*'; + +promise_test( + () => navigator.serial.getPorts(), + header + ' allows the top-level document.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, same_origin_src, + expect_feature_available_default); +}, header + ' allows same-origin iframes.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, same_origin_worker_frame_src, + expect_feature_available_default); +}, header + ' allows workers in same-origin iframes.'); + +// Set allow="serial" on iframe element to delegate 'serial' to cross origin +// subframe. +async_test(t => { + test_feature_availability('serial.getPorts()', t, cross_origin_src, + expect_feature_available_default, 'serial'); +}, header + ' allows cross-origin iframes.'); + +// Set allow="serial" on iframe element to delegate 'serial' to cross origin +// subframe. +async_test(t => { + test_feature_availability('serial.getPorts()', t, + cross_origin_worker_frame_src, + expect_feature_available_default, 'serial'); +}, header + ' allows workers in cross-origin iframes.'); + +fetch_tests_from_worker(new Worker( + 'resources/serial-allowed-by-permissions-policy-worker.js')); +</script> +</body> diff --git a/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html.headers new file mode 100644 index 0000000000..27deb82758 --- /dev/null +++ b/testing/web-platform/tests/serial/serial-allowed-by-permissions-policy.https.sub.html.headers @@ -0,0 +1 @@ +Permissions-Policy: serial=* diff --git a/testing/web-platform/tests/serial/serial-default-permissions-policy.https.sub.html b/testing/web-platform/tests/serial/serial-default-permissions-policy.https.sub.html new file mode 100644 index 0000000000..e3908d9215 --- /dev/null +++ b/testing/web-platform/tests/serial/serial-default-permissions-policy.https.sub.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<body> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/permissions-policy/resources/permissions-policy.js></script> +<script> +'use strict'; +var same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html'; +var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + + same_origin_src; +var header = 'Default "serial" permissions policy "self"'; + +promise_test( + () => navigator.serial.getPorts(), + header + ' allows the top-level document.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, same_origin_src, + expect_feature_available_default); +}, header + ' allows same-origin iframes.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, cross_origin_src, + expect_feature_unavailable_default); +}, header + ' disallows cross-origin iframes.'); +</script> +</body> diff --git a/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html new file mode 100644 index 0000000000..53646c526c --- /dev/null +++ b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/permissions-policy/resources/permissions-policy.js"></script> +<script> +'use strict'; +const sub = 'https://{{domains[www]}}:{{ports[https][0]}}'; +const same_origin_src = '/permissions-policy/resources/permissions-policy-serial.html'; +const cross_origin_src = sub + same_origin_src; +const same_origin_worker_frame_src = + '/permissions-policy/resources/permissions-policy-serial-worker.html'; +const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src; +const header = 'Permissions-Policy header serial=()'; + +promise_test(() => { + return navigator.serial.getPorts().then(() => { + assert_unreached('expected promise to reject with SecurityError'); + }, error => { + assert_equals(error.name, 'SecurityError'); + }); +}, header + ' disallows getPorts in the top-level document.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, same_origin_src, + expect_feature_unavailable_default); +}, header + ' disallows same-origin iframes.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, same_origin_worker_frame_src, + expect_feature_unavailable_default); +}, header + ' disallows workers in same-origin iframes.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, cross_origin_src, + expect_feature_unavailable_default); +}, header + ' disallows cross-origin iframes.'); + +async_test(t => { + test_feature_availability('serial.getPorts()', t, + cross_origin_worker_frame_src, + expect_feature_unavailable_default); +}, header + ' disallows workers in cross-origin iframes.'); + +fetch_tests_from_worker(new Worker( + 'resources/serial-disabled-by-permissions-policy-worker.js')); +</script> +</body> diff --git a/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html.headers new file mode 100644 index 0000000000..690b696751 --- /dev/null +++ b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html.headers @@ -0,0 +1 @@ +Permissions-Policy: serial=() diff --git a/testing/web-platform/tests/serial/serialPort_close.https.any.js b/testing/web-platform/tests/serial/serialPort_close.https.any.js new file mode 100644 index 0000000000..ec949a9917 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_close.https.any.js @@ -0,0 +1,26 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await promise_rejects_dom(t, 'InvalidStateError', port.close()); +}, 'A SerialPort cannot be closed if it was never opened.'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + await port.close(); + await promise_rejects_dom(t, 'InvalidStateError', port.close()); +}, 'A SerialPort cannot be closed if it is already closed.'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + const closePromise = port.close(); + await promise_rejects_dom(t, 'InvalidStateError', port.close()); + await closePromise; +}, 'A SerialPort cannot be closed if it is being closed.'); diff --git a/testing/web-platform/tests/serial/serialPort_disconnect-manual.https.html b/testing/web-platform/tests/serial/serialPort_disconnect-manual.https.html new file mode 100644 index 0000000000..5386201e6c --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_disconnect-manual.https.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/common.js"></script> + <script src="resources/manual.js"></script> + </head> + <body> + <p> + These tests require a serial device to be connected and disconnected. + </p> + <script> + manual_serial_test(async (t, port) => { + const watcher = new EventWatcher(t, navigator.serial, ['disconnect']); + + await port.open({baudRate: 115200, bufferSize: 1024}); + + const disconnectPromise = watcher.wait_for(['disconnect']) + const reader = port.readable.getReader(); + + const disconnectMessage = document.createElement('div'); + disconnectMessage.textContent = 'Disconnect the device now.'; + document.body.appendChild(disconnectMessage); + + try { + while (true) { + const {value, done} = await reader.read(); + // Ignore |value| in case the device happens to produce data. It is + // not important for this test. + assert_false(done); + } + } catch (e) { + assert_equals(e.constructor, DOMException); + assert_equals(e.name, 'NetworkError'); + } + reader.releaseLock(); + assert_equals(port.readable, null); + + let event = await disconnectPromise; + assert_equals(event.target, port); + + disconnectMessage.remove(); + + await port.close(); + }, 'Disconnect during read is detected.'); + + manual_serial_test(async (t, port) => { + const watcher = new EventWatcher(t, navigator.serial, ['disconnect']); + + await port.open({baudRate: 115200, bufferSize: 1024}); + + const disconnectPromise = watcher.wait_for(['disconnect']) + const writer = port.writable.getWriter(); + + const disconnectMessage = document.createElement('div'); + disconnectMessage.textContent = 'Disconnect the device now.'; + document.body.appendChild(disconnectMessage); + + const data = new Uint8Array(4096); + try { + while (true) { + await writer.write(data); + } + } catch (e) { + assert_equals(e.constructor, DOMException); + assert_equals(e.name, 'NetworkError'); + } + writer.releaseLock(); + assert_equals(port.writable, null); + + let event = await disconnectPromise; + assert_equals(event.target, port); + + disconnectMessage.remove(); + + await port.close(); + }, 'Disconnect during write is detected.'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/serial/serialPort_events.https.any.js b/testing/web-platform/tests/serial/serialPort_events.https.any.js new file mode 100644 index 0000000000..fa3db6a3b3 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_events.https.any.js @@ -0,0 +1,197 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const targets = [navigator.serial, port]; + const expectedTargets = [navigator.serial]; + + const actualTargets = []; + function eventHandler(evt) { + actualTargets.push(evt.currentTarget); + + if (evt.currentTarget == navigator.serial) { + evt.stopPropagation(); + } + } + + targets.forEach((target) => { + target.addEventListener('foo', eventHandler, {capture: true}); + // stopPropagation() during capturing prevents bubbling. + target.addEventListener('foo', eventHandler); + + t.add_cleanup(() => { + target.removeEventListener('foo', eventHandler, {capture: true}); + target.removeEventListener('foo', eventHandler); + }); + }); + + port.dispatchEvent(new CustomEvent('foo', {bubbles: true})); + + assert_array_equals(actualTargets, expectedTargets, 'actualTargets'); +}, 'stopPropagation() during capturing'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const targets = [navigator.serial, port]; + const expectedTargets = [navigator.serial]; + + const actualTargets = []; + function eventHandler(evt) { + actualTargets.push(evt.currentTarget); + + if (evt.currentTarget == navigator.serial) { + evt.cancelBubble = true; + } + } + + targets.forEach((target) => { + target.addEventListener('foo', eventHandler, {capture: true}); + // Setting cancelBubble during capturing prevents bubbling. + target.addEventListener('foo', eventHandler); + + t.add_cleanup(() => { + target.removeEventListener('foo', eventHandler, {capture: true}); + target.removeEventListener('foo', eventHandler); + }); + }); + + port.dispatchEvent(new CustomEvent('foo', {bubbles: true})); + + assert_array_equals(actualTargets, expectedTargets, 'actualTargets'); +}, 'Set cancelBubble during capturing'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const targets = [navigator.serial, port]; + const expectedTargets = [port]; + + const actualTargets = []; + function eventHandler(evt) { + actualTargets.push(evt.currentTarget); + + if (evt.currentTarget == port) { + evt.stopPropagation(); + } + } + + targets.forEach((target) => { + target.addEventListener('foo', eventHandler); + + t.add_cleanup(() => { + target.removeEventListener('foo', eventHandler); + }); + }); + + port.dispatchEvent(new CustomEvent('foo', {bubbles: true})); + + assert_array_equals(actualTargets, expectedTargets, 'actualTargets'); +}, 'stopPropagation() during bubbling'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const targets = [navigator.serial, port]; + const expectedTargets = [port]; + + const actualTargets = []; + function eventHandler(evt) { + actualTargets.push(evt.currentTarget); + + if (evt.currentTarget == port) { + evt.cancelBubble = true; + } + } + + targets.forEach((target) => { + target.addEventListener('foo', eventHandler); + + t.add_cleanup(() => { + target.removeEventListener('foo', eventHandler); + }); + }); + + port.dispatchEvent(new CustomEvent('foo', {bubbles: true})); + + assert_array_equals(actualTargets, expectedTargets, 'actualTargets'); +}, 'Set cancelBubble during bubbling'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const targets = [navigator.serial, port]; + const expectedTargets = [ + navigator.serial, + port, + navigator.serial, + port, + ]; + const expectedTypes = [ + 'foo', + 'bar', + 'bar', + 'foo', + ]; + + const actualTargets = []; + const actualTypes = []; + function eventHandler(evt) { + actualTargets.push(evt.currentTarget); + actualTypes.push(evt.type); + + if (evt.currentTarget == navigator.serial && evt.type == 'foo') { + port.dispatchEvent(new CustomEvent('bar', {bubbles: true})); + } + } + + targets.forEach((target) => { + target.addEventListener('foo', eventHandler, {capture: true}); + target.addEventListener('bar', eventHandler); + + t.add_cleanup(() => { + target.removeEventListener('foo', eventHandler, {capture: true}); + target.removeEventListener('bar', eventHandler); + }); + }); + + port.dispatchEvent(new CustomEvent('foo', {bubbles: true})); + + assert_array_equals(actualTargets, expectedTargets, 'actualTargets'); + assert_array_equals(actualTypes, expectedTypes, 'actualTypes'); +}, 'An event dispatched in an event handler is propagated before continuing'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const targets = [navigator.serial, port]; + const expected = [ + 'capturing Serial', + 'capturing SerialPort', + 'bubbling SerialPort', + 'bubbling Serial', + ]; + + const actual = []; + targets.forEach((target) => { + const bubblingEventHandler = () => { + actual.push(`bubbling ${target.constructor.name}`); + }; + target.addEventListener('foo', bubblingEventHandler); + const capturingEventHandler = () => { + actual.push(`capturing ${target.constructor.name}`); + }; + target.addEventListener('foo', capturingEventHandler, {capture: true}); + + t.add_cleanup(() => { + target.removeEventListener('foo', bubblingEventHandler, {capture: true}); + target.removeEventListener('foo', capturingEventHandler); + }); + }); + + port.dispatchEvent(new CustomEvent('foo', {bubbles: true})); + assert_array_equals(actual, expected); +}, 'Capturing and bubbling events delivered to listeners in the expected order'); diff --git a/testing/web-platform/tests/serial/serialPort_forget.https.any.js b/testing/web-platform/tests/serial/serialPort_forget.https.any.js new file mode 100644 index 0000000000..8c0d96f695 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_forget.https.any.js @@ -0,0 +1,43 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.forget(); + return promise_rejects_dom( + t, 'NetworkError', port.open({baudRate: 9600})); +}, 'open() rejects after forget()'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + await port.forget(); + return promise_rejects_dom(t, 'InvalidStateError', port.close()); +}, 'close() rejects after forget()'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + await port.forget(); + return promise_rejects_dom(t, 'InvalidStateError', port.setSignals()); +}, 'setSignals() rejects after forget()'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + await port.forget(); + return promise_rejects_dom(t, 'InvalidStateError', port.getSignals()); +}, 'getSignals() rejects after forget()'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + const portsBeforeForget = await navigator.serial.getPorts(); + assert_equals(portsBeforeForget.length, 1); + assert_equals(portsBeforeForget[0], port); + + await port.forget(); + + const portsAfterForget = await navigator.serial.getPorts(); + assert_equals(portsAfterForget.length, 0); +}, 'forget() removes the device from getPorts()');
\ No newline at end of file diff --git a/testing/web-platform/tests/serial/serialPort_getInfo.https.any.js b/testing/web-platform/tests/serial/serialPort_getInfo.https.any.js new file mode 100644 index 0000000000..2e0df3b8bd --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_getInfo.https.any.js @@ -0,0 +1,24 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + // Wait for getPorts() to resolve in order to ensure that the Mojo client + // interface has been configured. + let ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 0); + + [{}, + {usbVendorId: 1}, + {usbProductId: 2}, + {usbVendorId: 1, usbProductId: 2}, + ].forEach((expectedInfo) => { + serial_test(async (t, fake) => { + let watcher = new EventWatcher(t, navigator.serial, ['connect']); + fake.addPort(expectedInfo); + let evt = await watcher.wait_for(['connect']); + let info = evt.target.getInfo(); + assert_object_equals(expectedInfo, info); + }, `getInfo() returns ${JSON.stringify(expectedInfo)}`); + }); +}, 'getInfo() meta test'); diff --git a/testing/web-platform/tests/serial/serialPort_getSignals.https.any.js b/testing/web-platform/tests/serial/serialPort_getSignals.https.any.js new file mode 100644 index 0000000000..a570e35987 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_getSignals.https.any.js @@ -0,0 +1,63 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await promise_rejects_dom(t, 'InvalidStateError', port.getSignals()); +}, 'getSignals() rejects if the port is not open'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + + let expectedSignals = { + dataCarrierDetect: false, + clearToSend: false, + ringIndicator: false, + dataSetReady: false + }; + fakePort.simulateInputSignals(expectedSignals); + let signals = await port.getSignals(); + assert_object_equals(signals, expectedSignals); + + expectedSignals.dataCarrierDetect = true; + fakePort.simulateInputSignals(expectedSignals); + signals = await port.getSignals(); + assert_object_equals(signals, expectedSignals, 'DCD set'); + + expectedSignals.clearToSend = true; + fakePort.simulateInputSignals(expectedSignals); + signals = await port.getSignals(); + assert_object_equals(signals, expectedSignals, 'CTS set'); + + expectedSignals.ringIndicator = true; + fakePort.simulateInputSignals(expectedSignals); + signals = await port.getSignals(); + assert_object_equals(signals, expectedSignals, 'RI set'); + + expectedSignals.dataSetReady = true; + fakePort.simulateInputSignals(expectedSignals); + signals = await port.getSignals(); + assert_object_equals(signals, expectedSignals, 'DSR set'); +}, 'getSignals() returns the current state of input control signals'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + + fakePort.simulateInputSignalFailure(true); + await promise_rejects_dom(t, 'NetworkError', port.getSignals()); + + fakePort.simulateInputSignalFailure(false); + const expectedSignals = { + dataCarrierDetect: false, + clearToSend: false, + ringIndicator: false, + dataSetReady: false + }; + const signals = await port.getSignals(); + assert_object_equals(signals, expectedSignals); + + await port.close(); +}, 'getSignals() rejects on failure'); diff --git a/testing/web-platform/tests/serial/serialPort_loopback-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback-manual.https.html new file mode 100644 index 0000000000..bf876b6610 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_loopback-manual.https.html @@ -0,0 +1,97 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/common.js"></script> + <script src="resources/manual.js"></script> + </head> + <body> + <p> + These tests require a connected serial device configured to act as a + "loopback" device, with the transmit and receive pins wired together. + </p> + <script> + manual_serial_test(async (t, port) => { + await port.open({baudRate: 115200, bufferSize: 1024}); + + // Create something much smaller than bufferSize above. + const data = new Uint8Array(64); + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + + const reader = port.readable.getReader(); + + for (let i = 0; i < 10; ++i) { + const writer = port.writable.getWriter(); + writer.write(data); + const writePromise = writer.close(); + + const value = await readWithLength(reader, data.byteLength); + await writePromise; + + compareArrays(value, data); + } + + reader.releaseLock(); + await port.close(); + }, 'Can perform a series of small writes.'); + + manual_serial_test(async (t, port) => { + await port.open({baudRate: 115200, bufferSize: 1024}); + + // Create something much larger than bufferSize above. + const data = new Uint8Array(10 * 1024); + for (let i = 0; i < data.byteLength; ++i) + data[i] = (i / 1024) & 0xff; + + const reader = port.readable.getReader(); + + for (let i = 0; i < 10; ++i) { + const writer = port.writable.getWriter(); + writer.write(data); + const writePromise = writer.close(); + + const value = await readWithLength(reader, data.byteLength); + await writePromise; + + compareArrays(value, data); + } + + reader.releaseLock(); + await port.close(); + }, 'Can perform a series of large writes.'); + + manual_serial_test(async (t, port) => { + await port.open({baudRate: 115200, bufferSize: 64}); + + const writer = port.writable.getWriter(); + // |data| is small enough to be completely transmitted. + let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + await writer.write(data); + + // Wait a little bit for the device to process the incoming data. + await new Promise((resolve) => setTimeout(resolve, 100)); + // ...before discarding the receive buffers. + await port.readable.cancel(); + + data = new Uint8Array([9, 10, 11, 12, 13, 14, 15, 16]); + const reader = port.readable.getReader(); + const readPromise = readWithLength(reader, data.byteLength); + + // The next block of data should be received successfully. + await writer.write(data); + writer.releaseLock(); + + const value = await readPromise; + reader.releaseLock(); + + compareArrays(value, data); + + await port.close(); + }, 'Canceling the reader discards buffered data.'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/serial/serialPort_loopback_BreakError-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback_BreakError-manual.https.html new file mode 100644 index 0000000000..369fac6155 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_loopback_BreakError-manual.https.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/common.js"></script> + <script src="resources/manual.js"></script> + </head> + <body> + <p> + These tests require a connected serial device configured to act as a + "loopback" device, with the transmit and receive pins wired together. + </p> + <script> + manual_serial_test(async (t, port) => { + await port.open({baudRate: 115200, bufferSize: 1024}); + + let reader = port.readable.getReader(); + let readPromise = (async () => { + // A single zero byte will be read before the break is detected. + const {value, done} = await reader.read(); + compareArrays(value, new Uint8Array([0])); + assert_false(done); + + try { + const {value, done} = await reader.read(); + assert_unreached(`Expected break, got ${value.byteLength} bytes`); + } catch (e) { + assert_equals(e.constructor, DOMException); + assert_equals(e.name, 'BreakError'); + } + })(); + + await port.setSignals({break: true}); + await readPromise; + await port.setSignals({break: false}); + + const writer = port.writable.getWriter(); + // |data| is small enough to be completely transmitted. + let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + await writer.write(data); + writer.releaseLock(); + + reader = port.readable.getReader(); + const buffer = await readWithLength(reader, data.byteLength);; + compareArrays(buffer, data); + reader.releaseLock(); + + await port.close(); + }, 'Break is detected.'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/serial/serialPort_loopback_BufferOverrunError-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback_BufferOverrunError-manual.https.html new file mode 100644 index 0000000000..06f8271ac9 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_loopback_BufferOverrunError-manual.https.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/common.js"></script> + <script src="resources/manual.js"></script> + </head> + <body> + <p> + These tests require a connected serial device configured to act as a + "loopback" device, with the transmit and receive pins wired together. + </p> + <script> + manual_serial_test(async (t, port) => { + await port.open({baudRate: 115200, bufferSize: 1024}); + + // Create something much larger than bufferSize above. + const data = new Uint8Array(16 * 1024); + for (let i = 0; i < data.byteLength; ++i) + data[i] = (i / 1024) & 0xff; + + // Completely write |data| to the port without waiting for it to be + // received. + const writer = port.writable.getWriter(); + writer.write(data); + await writer.close(); + + const reader = port.readable.getReader(); + const chunks = []; + let actualLength = 0; + while (true) { + try { + const {value, done} = await reader.read(); + if (value) { + actualLength += value.byteLength; + chunks.push(value); + } + if (done) { + assert_unreached("Unexpected end of stream."); + break; + } + } catch (e) { + assert_equals(e.name, 'BufferOverrunError'); + break; + } + } + reader.releaseLock(); + + const buffer = new Uint8Array(actualLength); + chunks.reduce((offset, chunk) => { + buffer.set(chunk, offset); + return offset + chunk.byteLength; + }, 0); + + assert_greater_than(actualLength, 0); + compareArrays(buffer, data.slice(0, actualLength)); + + await port.close(); + }, 'Overflowing the receive buffer triggers an error.'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/serial/serialPort_loopback_flowControl-manual.https.html b/testing/web-platform/tests/serial/serialPort_loopback_flowControl-manual.https.html new file mode 100644 index 0000000000..930dab74d6 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_loopback_flowControl-manual.https.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/common.js"></script> + <script src="resources/manual.js"></script> + </head> + <body> + <p> + These tests require a connected serial device configured to act as a + "loopback" device, with the TX and RX pins and RTS and CTS pins wired + together. + </p> + <script> + manual_serial_test(async (t, port) => { + await port.open({ + baudRate: 115200, + bufferSize: 255, + flowControl: 'none' + }); + t.add_cleanup(async () => { + await port.close(); + }); + + await port.setSignals({ requestToSend: true }); + let signals = await port.getSignals(); + assert_true(signals.clearToSend); + + await port.setSignals({ requestToSend: false }); + signals = await port.getSignals(); + assert_false(signals.clearToSend); + }, 'Manual RTS control works with no flow control enabled'); + + manual_serial_test(async (t, port) => { + await port.open({ + baudRate: 115200, + bufferSize: 255, + flowControl: 'hardware' + }); + + const writer = port.writable.getWriter(); + t.add_cleanup(async () => { + writer.releaseLock(); + await port.close(); + }); + + assert_true((await port.getSignals()).clearToSend); + + const buffer = new Uint8Array(1); + while ((await port.getSignals()).clearToSend) { + await writer.write(buffer); + } + }, 'Hardware flow control automatically sets RTS pin'); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/serial/serialPort_ondisconnect.https.any.js b/testing/web-platform/tests/serial/serialPort_ondisconnect.https.any.js new file mode 100644 index 0000000000..b20f69d265 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_ondisconnect.https.any.js @@ -0,0 +1,40 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + // Don't listen for 'disconnect' events on navigator.serial so we can listen + // for them on each SerialPort instance instead. + const eventWatcher = new EventWatcher(t, navigator.serial, ['connect']); + + // Wait for getPorts() to resolve in order to ensure that the Mojo client + // interface has been configured. + let ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 0); + + // Add ports one at a time so that we can map tokens to ports. + const token1 = fake.addPort(); + const port1 = (await eventWatcher.wait_for(['connect'])).target; + const port1Watcher = new EventWatcher(t, port1, ['disconnect']); + + const token2 = fake.addPort(); + const port2 = (await eventWatcher.wait_for(['connect'])).target; + const port2Watcher = new EventWatcher(t, port2, ['disconnect']); + + fake.removePort(token2); + const event1 = await port2Watcher.wait_for(['disconnect']); + assert_true(event1 instanceof Event); + assert_equals(event1.target, port2); + + ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 1); + assert_equals(ports[0], port1); + + fake.removePort(token1); + const event2 = await port1Watcher.wait_for(['disconnect']); + assert_true(event2 instanceof Event); + assert_equals(event2.target, port1); + + ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 0); +}, 'A "disconnect" event is fired on ports when they are removed.'); diff --git a/testing/web-platform/tests/serial/serialPort_open.https.any.js b/testing/web-platform/tests/serial/serialPort_open.https.any.js new file mode 100644 index 0000000000..ca13877088 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_open.https.any.js @@ -0,0 +1,95 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + return promise_rejects_dom( + t, 'InvalidStateError', port.open({baudRate: 9600})); +}, 'A SerialPort cannot be opened if it is already open.'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const firstRequest = port.open({baudRate: 9600}); + await promise_rejects_dom( + t, 'InvalidStateError', port.open({baudRate: 9600})); + await firstRequest; +}, 'Simultaneous calls to open() are disallowed.'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await promise_rejects_js(t, TypeError, port.open({})); + + await Promise.all([-1, 0].map( + baudRate => { + return promise_rejects_js(t, TypeError, port.open({baudRate}))})); +}, 'Baud rate is required and must be greater than zero.'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await Promise.all([-1, 0, 6, 9].map(dataBits => { + return promise_rejects_js( + t, TypeError, port.open({baudRate: 9600, dataBits})); + })); + + await[undefined, 7, 8].reduce(async (previousTest, dataBits) => { + await previousTest; + await port.open({baudRate: 9600, dataBits}); + await port.close(); + }, Promise.resolve()); +}, 'Data bits must be 7 or 8'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await Promise.all([0, null, 'cats'].map(parity => { + return promise_rejects_js( + t, TypeError, port.open({baudRate: 9600, parity}), + `Should reject parity option "${parity}"`); + })); + + await[undefined, 'none', 'even', 'odd'].reduce( + async (previousTest, parity) => { + await previousTest; + await port.open({baudRate: 9600, parity}); + await port.close(); + }, + Promise.resolve()); +}, 'Parity must be "none", "even" or "odd"'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await Promise.all([-1, 0, 3, 4].map(stopBits => { + return promise_rejects_js( + t, TypeError, port.open({baudRate: 9600, stopBits})); + })); + + await[undefined, 1, 2].reduce(async (previousTest, stopBits) => { + await previousTest; + await port.open({baudRate: 9600, stopBits}); + await port.close(); + }, Promise.resolve()); +}, 'Stop bits must be 1 or 2'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await promise_rejects_js( + t, TypeError, port.open({baudRate: 9600, bufferSize: -1})); + await promise_rejects_js( + t, TypeError, port.open({baudRate: 9600, bufferSize: 0})); +}, 'Buffer size must be greater than zero.'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + const bufferSize = 1 * 1024 * 1024 * 1024 /* 1 GiB */; + return promise_rejects_js( + t, TypeError, port.open({baudRate: 9600, bufferSize})); +}, 'Unreasonably large buffer sizes are rejected.'); diff --git a/testing/web-platform/tests/serial/serialPort_readable-manual.https.html b/testing/web-platform/tests/serial/serialPort_readable-manual.https.html new file mode 100644 index 0000000000..4e49ef4061 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable-manual.https.html @@ -0,0 +1,107 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/common.js"></script> + <script src="resources/manual.js"></script> + </head> + <body> + <p> + These tests require a device configured with the following Arduino sketch: + + <pre> +uint32_t seed = 0; +uint32_t bytesToSend = 0; + +uint8_t nextByte() { + seed = (1103515245 * seed + 12345) % 0x8000000; + return (seed >> 16) & 0xFF; +} + +void setup() { + Serial.begin(115200); +} + +void loop() { + if (!Serial) { + return; + } + + if (bytesToSend == 0) { + // Read the seed and number of bytes to send from the host as 32-bit + // little-endian values. + if (Serial.available() < 8) { + return; + } + + uint8_t buf[8]; + Serial.readBytes((char*)buf, sizeof buf); + seed = (uint32_t)buf[0] | + ((uint32_t)buf[1] << 8) | + ((uint32_t)buf[2] << 16) | + ((uint32_t)buf[3] << 24); + bytesToSend = (uint32_t)buf[4] | + ((uint32_t)buf[5] << 8) | + ((uint32_t)buf[6] << 16) | + ((uint32_t)buf[7] << 24); + } else { + uint8_t buf[64]; + uint32_t count = min(sizeof buf, bytesToSend); + for (uint32_t i = 0; i < count; ++i) { + buf[i] = nextByte(); + } + bytesToSend -= count; + Serial.write((char*)buf, count); + } +} + </pre> + </p> + <p> + <progress id='progress'></progress> + </p> + <script> + let seed = 10; + const length = 1024 * 1024 * 10; + + function next_byte() { + seed = (Math.imul(1103515245, seed) + 12345) % (1 << 31); + return (seed >> 16) & 0xFF; + } + + manual_serial_test(async (t, port) => { + await port.open({baudRate: 115200, bufferSize: 1024}); + + const config = new DataView(new ArrayBuffer(8)); + config.setUint32(0, seed, /*littleEndian=*/true); + config.setUint32(4, length, /*littleEndian=*/true); + + const writer = port.writable.getWriter(); + writer.write(config); + + const progress = document.getElementById('progress'); + progress.max = length; + progress.value = 0; + + const reader = port.readable.getReader(); + let bytesRead = 0; + while (bytesRead < length) { + const { value, done } = await reader.read(); + assert_false(done); + for (let i = 0; i < value.byteLength; ++i) { + assert_equals(value[i], next_byte(), + `mismatch at byte ${bytesRead + i}`); + } + bytesRead += value.byteLength; + progress.value = bytesRead; + } + + writer.releaseLock(); + reader.releaseLock(); + await port.close(); + }, `Reading ${length} bytes from the device succeeds.`); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/serial/serialPort_readable_byob.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_byob.https.any.js new file mode 100644 index 0000000000..8672cf7124 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_byob.https.any.js @@ -0,0 +1,65 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +async function readInto(reader, buffer) { + let offset = 0; + while (offset < buffer.byteLength) { + const {value: view, done} = + await reader.read(new Uint8Array(buffer, offset)); + buffer = view.buffer; + if (done) { + break; + } + offset += view.byteLength; + } + return buffer; +} + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + const bufferSize = 1024; + await port.open({baudRate: 9600, bufferSize}); + + const reader = port.readable.getReader({mode: 'byob'}); + assert_true(reader instanceof ReadableStreamBYOBReader); + + await fakePort.writable(); + const data = new Uint8Array(bufferSize); + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + fakePort.write(data); + + let buffer = new ArrayBuffer(512); + buffer = await readInto(reader, buffer); + assert_equals(512, buffer.byteLength, 'original size retained'); + compareArrays(data.subarray(0, 512), new Uint8Array(buffer)); + + buffer = await readInto(reader, buffer); + assert_equals(512, buffer.byteLength, 'original size retained'); + compareArrays(data.subarray(512), new Uint8Array(buffer)); + reader.releaseLock(); + + await port.close(); +}, 'Can read specific subsets of the available data'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader({mode: 'byob'}); + assert_true(reader instanceof ReadableStreamBYOBReader); + + await fakePort.writable(); + const data = new Uint8Array(1024); + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + fakePort.write(data); + + let buffer = new ArrayBuffer(1024); + buffer = await readInto(reader, buffer); + compareArrays(data, new Uint8Array(buffer)); + reader.releaseLock(); + + await port.close(); +}, 'Can read a large amount of data over multiple iterations'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_cancel.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_cancel.https.any.js new file mode 100644 index 0000000000..fd1b08f6e7 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_cancel.https.any.js @@ -0,0 +1,69 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader(); + const readPromise = reader.read(); + await reader.cancel(); + const {value, done} = await readPromise; + assert_true(done); + assert_equals(undefined, value); + + await port.close(); +}, 'Can cancel while reading'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader(); + const closed = (async () => { + const {value, done} = await reader.read(); + assert_true(done); + assert_equals(undefined, value); + reader.releaseLock(); + await port.close(); + assert_equals(port.readable, null); + })(); + + await reader.cancel(); + await closed; +}, 'Can close while canceling'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader(); + + await fakePort.writable(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + await fakePort.write(data); + + await reader.cancel(); + await port.close(); +}, 'Cancel discards a small amount of data waiting to be read'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader(); + + await fakePort.writable(); + const data = new Uint8Array(1024); + // Writing will fail because there was more data to send than could fit in the + // buffer and none of it was read. + const writePromise = + promise_rejects_dom(t, 'InvalidStateError', fakePort.write(data)); + + await reader.cancel(); + await writePromise; + + await port.close(); +}, 'Cancel discards a large amount of data waiting to be read'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_chain.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_chain.https.any.js new file mode 100644 index 0000000000..552567cdfe --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_chain.https.any.js @@ -0,0 +1,30 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size larger than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const decoder = new TextDecoderStream(); + const streamClosed = port.readable.pipeTo(decoder.writable); + const readable = decoder.readable.pipeThrough(new TransformStream()) + .pipeThrough(new TransformStream()) + .pipeThrough(new TransformStream()) + .pipeThrough(new TransformStream()); + const reader = readable.getReader(); + + await fakePort.writable(); + fakePort.write(new TextEncoder().encode('Hello world!')); + + const {value, done} = await reader.read(); + assert_false(done); + assert_equals('Hello world!', value); + await reader.cancel('arbitrary reason'); + await streamClosed.catch(reason => { + assert_equals('arbitrary reason', reason); + }); + + await port.close(); +}, 'Stream closure is observable through a long chain of transforms'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_closeLocked.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_closeLocked.https.any.js new file mode 100644 index 0000000000..21196f758b --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_closeLocked.https.any.js @@ -0,0 +1,17 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + assert_true(port.readable instanceof ReadableStream); + + const reader = port.readable.getReader(); + await promise_rejects_js(t, TypeError, port.close()); + + reader.releaseLock(); + await port.close(); + assert_equals(port.readable, null); +}, 'Port cannot be closed while readable is locked'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_disconnect.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_disconnect.https.any.js new file mode 100644 index 0000000000..3532745ae0 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_disconnect.https.any.js @@ -0,0 +1,25 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader(); + + await fakePort.writable(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + fakePort.write(data); + fakePort.simulateDisconnectOnRead(); + + const {value, done} = await reader.read(); + assert_false(done); + compareArrays(data, value); + + await promise_rejects_dom(t, 'NetworkError', reader.read()); + assert_equals(port.readable, null); + + await port.close(); +}, 'Disconnect error closes readable and sets it to null'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_largeRead.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_largeRead.https.any.js new file mode 100644 index 0000000000..f8087514f7 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_largeRead.https.any.js @@ -0,0 +1,23 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader(); + + await fakePort.writable(); + const data = new Uint8Array(1024); // Much larger than bufferSize above. + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + fakePort.write(data); + + const value = await readWithLength(reader, data.byteLength); + compareArrays(data, value); + reader.releaseLock(); + + await port.close(); +}, 'Can read a large amount of data'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_open.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_open.https.any.js new file mode 100644 index 0000000000..1a12cc2da4 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_open.https.any.js @@ -0,0 +1,21 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + assert_equals(port.readable, null); + + await port.open({baudRate: 9600}); + const readable = port.readable; + assert_true(readable instanceof ReadableStream); + + await port.close(); + assert_equals(port.readable, null); + + const reader = readable.getReader(); + const {value, done} = await reader.read(); + assert_true(done); + assert_equals(value, undefined); +}, 'SerialPort.readable is set by open() and closes on port close'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_parityError.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_parityError.https.any.js new file mode 100644 index 0000000000..7afd494dc5 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_parityError.https.any.js @@ -0,0 +1,52 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +// ParityError is not (as of 2020/03/23) a valid DOMException, so cannot use +// promise_rejects_dom for it. +async function promise_rejects_with_parity_error(t, promise) { + return promise + .then(() => { + assert_false('Should have rejected'); + }) + .catch(e => { + assert_equals(e.constructor, DOMException); + assert_equals(e.name, 'ParityError'); + }); +} + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + let readable = port.readable; + let reader = readable.getReader(); + + await fakePort.writable(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + fakePort.write(data); + fakePort.simulateParityError(); + + let {value, done} = await reader.read(); + assert_false(done); + compareArrays(data, value); + + await promise_rejects_with_parity_error(t, reader.read()); + assert_not_equals(port.readable, readable); + + readable = port.readable; + assert_true(readable instanceof ReadableStream); + reader = port.readable.getReader(); + + await fakePort.writable(); + fakePort.write(data); + + ({value, done} = await reader.read()); + assert_false(done); + compareArrays(data, value); + reader.releaseLock(); + + await port.close(); + assert_equals(port.readable, null); +}, 'Parity error closes readable and replaces it with a new stream'); diff --git a/testing/web-platform/tests/serial/serialPort_readable_pipeThrough.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_pipeThrough.https.any.js new file mode 100644 index 0000000000..13c6d48023 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_pipeThrough.https.any.js @@ -0,0 +1,26 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size larger than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const decoder = new TextDecoderStream(); + const streamClosed = port.readable.pipeTo(decoder.writable); + const reader = decoder.readable.getReader(); + + await fakePort.writable(); + fakePort.write(new TextEncoder().encode('Hello world!')); + + const {value, done} = await reader.read(); + assert_false(done); + assert_equals('Hello world!', value); + await reader.cancel(); + await streamClosed.catch(reason => { + assert_equals(undefined, reason); + }); + + await port.close(); +}, 'Can pipe readable through a transform stream.') diff --git a/testing/web-platform/tests/serial/serialPort_readable_smallRead.https.any.js b/testing/web-platform/tests/serial/serialPort_readable_smallRead.https.any.js new file mode 100644 index 0000000000..b926aa608f --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_readable_smallRead.https.any.js @@ -0,0 +1,23 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size larger than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const reader = port.readable.getReader(); + assert_true(reader instanceof ReadableStreamDefaultReader); + + await fakePort.writable(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + fakePort.write(data); + + let {value, done} = await reader.read(); + assert_false(done); + compareArrays(data, value); + reader.releaseLock(); + + await port.close(); +}, 'Can read a small amount of data'); diff --git a/testing/web-platform/tests/serial/serialPort_setSignals.https.any.js b/testing/web-platform/tests/serial/serialPort_setSignals.https.any.js new file mode 100644 index 0000000000..27911ab7db --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_setSignals.https.any.js @@ -0,0 +1,59 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await promise_rejects_dom(t, 'InvalidStateError', port.setSignals({})); +}, 'setSignals() rejects if the port is not open'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + + let expectedSignals = { + dataTerminalReady: true, + requestToSend: false, + break: false + }; + assert_object_equals(fakePort.outputSignals, expectedSignals, 'initial'); + + await promise_rejects_js(t, TypeError, port.setSignals()); + assert_object_equals(fakePort.outputSignals, expectedSignals, 'no-op'); + + await promise_rejects_js(t, TypeError, port.setSignals({})); + assert_object_equals(fakePort.outputSignals, expectedSignals, 'no-op'); + + await port.setSignals({dataTerminalReady: false}); + expectedSignals.dataTerminalReady = false; + assert_object_equals(fakePort.outputSignals, expectedSignals, 'clear DTR'); + + await port.setSignals({requestToSend: true}); + expectedSignals.requestToSend = true; + assert_object_equals(fakePort.outputSignals, expectedSignals, 'set RTS'); + + await port.setSignals({break: true}); + expectedSignals.break = true; + assert_object_equals(fakePort.outputSignals, expectedSignals, 'set BRK'); + + await port.setSignals( + {dataTerminalReady: true, requestToSend: false, break: false}); + expectedSignals.dataTerminalReady = true; + expectedSignals.requestToSend = false; + expectedSignals.break = false; + assert_object_equals(fakePort.outputSignals, expectedSignals, 'invert'); +}, 'setSignals() modifies the state of the port'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + + fakePort.simulateOutputSignalFailure(true); + await promise_rejects_dom(t, 'NetworkError', port.setSignals({break: true})); + + fakePort.simulateOutputSignalFailure(false); + await port.setSignals({break: true}); + assert_true(fakePort.outputSignals.break); + + await port.close(); +}, 'setSignals() rejects on failure'); diff --git a/testing/web-platform/tests/serial/serialPort_writable.https.any.js b/testing/web-platform/tests/serial/serialPort_writable.https.any.js new file mode 100644 index 0000000000..087c881500 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_writable.https.any.js @@ -0,0 +1,289 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + assert_equals(port.writable, null); + + await port.open({baudRate: 9600}); + const writable = port.writable; + assert_true(writable instanceof WritableStream); + + await port.close(); + assert_equals(port.writable, null); + + const writer = writable.getWriter(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + await promise_rejects_dom(t, 'InvalidStateError', writer.write(data)); +}, 'open() and close() set and unset SerialPort.writable'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + assert_true(port.writable instanceof WritableStream); + + const writer = port.writable.getWriter(); + await promise_rejects_js(t, TypeError, port.close()); + + writer.releaseLock(); + await port.close(); + assert_equals(port.writable, null); +}, 'Port cannot be closed while writable is locked'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + assert_true(port.writable instanceof WritableStream); + + const writer = port.writable.getWriter(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + let writePromise = writer.write(data); + writer.releaseLock(); + + await fakePort.readable(); + let {value, done} = await fakePort.read(); + await writePromise; + compareArrays(value, data); + + await port.close(); + assert_equals(port.writable, null); +}, 'Can write a small amount of data'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const writer = port.writable.getWriter(); + const data = new Uint8Array(1024); // Much larger than bufferSize above. + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + writer.write(data); + writer.releaseLock(); + + await fakePort.readable(); + const value = await fakePort.readWithLength(data.byteLength); + compareArrays(data, value); + + await port.close(); +}, 'Can write a large amount of data'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + + const writable = port.writable; + assert_true(writable instanceof WritableStream); + let writer = writable.getWriter(); + + await fakePort.readable(); + fakePort.simulateSystemErrorOnWrite(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + await promise_rejects_dom(t, 'UnknownError', writer.write(data)); + + assert_true(port.writable instanceof WritableStream); + assert_not_equals(port.writable, writable); + + writer = port.writable.getWriter(); + let writePromise = writer.write(data); + writer.releaseLock(); + await fakePort.readable(); + let {value, done} = await fakePort.read(); + await writePromise; + compareArrays(value, data); + + await port.close(); + assert_equals(port.writable, null); +}, 'System error closes writable and replaces it with a new stream'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600}); + + assert_true(port.writable instanceof WritableStream); + const writer = port.writable.getWriter(); + + await fakePort.readable(); + fakePort.simulateDisconnectOnWrite(); + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + await promise_rejects_dom(t, 'NetworkError', writer.write(data)); + assert_equals(port.writable, null); + + await port.close(); +}, 'Disconnect error closes writable and sets it to null'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600, bufferSize: 64}); + const originalWritable = port.writable; + assert_true(originalWritable instanceof WritableStream); + + let writer = originalWritable.getWriter(); + let data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + // The buffer size is large enough to allow this write to complete without + // the data being read from the fake port. + await writer.write(data); + await writer.abort(); + + assert_true(port.writable instanceof WritableStream); + assert_true(port.writable !== originalWritable); + writer = port.writable.getWriter(); + data = new Uint8Array([9, 10, 11, 12, 13, 14, 15, 16]); + const writePromise = writer.write(data); + writer.releaseLock(); + + await fakePort.readable(); + const {value, done} = await fakePort.read(); + await writePromise; + compareArrays(value, data); + + await port.close(); + assert_equals(port.writable, null); +}, 'abort() discards the write buffer'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const writer = port.writable.getWriter(); + // Wait for microtasks to execute in order to ensure that the WritableStream + // has been set up completely. + await Promise.resolve(); + + const data = new Uint8Array(1024); // Much larger than bufferSize above. + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + const writePromise = + promise_rejects_exactly(t, 'Aborting.', writer.write(data)); + + await writer.abort('Aborting.'); + await writePromise; + await port.close(); + assert_equals(port.writable, null); +}, 'abort() does not wait for the write buffer to be cleared'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const writer = port.writable.getWriter(); + const data = new Uint8Array(1024); // Much larger than bufferSize above. + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + const closed = (async () => { + await promise_rejects_exactly(t, 'Aborting.', writer.write(data)); + writer.releaseLock(); + await port.close(); + assert_equals(port.writable, null); + })(); + + await writer.abort('Aborting.'); + await closed; +}, 'Can close while aborting'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const writer = port.writable.getWriter(); + const data = new Uint8Array(1024); // Much larger than bufferSize above. + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + writer.write(data); + + let readComplete = false; + let writePromise = writer.close().then(() => { + assert_true(readComplete); + }); + + await fakePort.readable(); + let readPromise = fakePort.readWithLength(data.byteLength).then(result => { + readComplete = true; + return result; + }); + const value = await readPromise; + compareArrays(data, value); + await writePromise; + + await port.close(); +}, 'close() waits for the write buffer to be cleared'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + const writer = port.writable.getWriter(); + // Wait for microtasks to execute in order to ensure that the WritableStream + // has been set up completely. + await Promise.resolve(); + + const data = new Uint8Array(1024); // Much larger than bufferSize above. + for (let i = 0; i < data.byteLength; ++i) + data[i] = i & 0xff; + const writePromise = + promise_rejects_exactly(t, 'Aborting.', writer.write(data)); + const closePromise = promise_rejects_exactly(t, 'Aborting.', writer.close()); + + await writer.abort('Aborting.'); + await writePromise; + await closePromise; + await port.close(); + assert_equals(port.writable, null); +}, 'Can abort while closing'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + assert_true(port.writable instanceof WritableStream); + + const encoder = new TextEncoderStream(); + const streamClosed = encoder.readable.pipeTo(port.writable); + const writer = encoder.writable.getWriter(); + const writePromise = writer.write('Hello world!'); + + await fakePort.readable(); + const {value, done} = await fakePort.read(); + await writePromise; + assert_equals('Hello world!', new TextDecoder().decode(value)); + await writer.close(); + await streamClosed; + + await port.close(); + assert_equals(port.writable, null); +}, 'Can pipe a stream to writable'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + + await port.open({baudRate: 9600}); + assert_true(port.writable instanceof WritableStream); + + const transform = new TransformStream(); + const readable = transform.readable.pipeThrough(new TextEncoderStream()) + .pipeThrough(new TransformStream()) + .pipeThrough(new TransformStream()) + .pipeThrough(new TransformStream()); + const streamClosed = readable.pipeTo(port.writable); + const writer = transform.writable.getWriter(); + const writePromise = writer.write('Hello world!'); + + await fakePort.readable(); + const {value, done} = await fakePort.read(); + await writePromise; + assert_equals('Hello world!', new TextDecoder().decode(value)); + await writer.close(); + await streamClosed; + + await port.close(); + assert_equals(port.writable, null); +}, 'Stream closure is observable through a long chain of transformers'); diff --git a/testing/web-platform/tests/serial/serialPort_writable_detachBuffer.https.any.js b/testing/web-platform/tests/serial/serialPort_writable_detachBuffer.https.any.js new file mode 100644 index 0000000000..828e877726 --- /dev/null +++ b/testing/web-platform/tests/serial/serialPort_writable_detachBuffer.https.any.js @@ -0,0 +1,48 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +function detachBuffer(buffer) { + const channel = new MessageChannel(); + channel.port1.postMessage('', [buffer]); +} + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + await port.open({baudRate: 9600, bufferSize: 64}); + + const writer = port.writable.getWriter(); + const data = new Uint8Array(64); + detachBuffer(data.buffer); + + // Writing a detached buffer is equivalent to writing an empty buffer so this + // should trivially succeed. + await writer.write(data); + writer.releaseLock(); + + await port.close(); +}, 'Writing a detached buffer is safe'); + +serial_test(async (t, fake) => { + const {port, fakePort} = await getFakeSerialPort(fake); + // Select a buffer size smaller than the amount of data transferred. + await port.open({baudRate: 9600, bufferSize: 64}); + + // Start writing a buffer much larger than bufferSize above so that it can't + // all be transfered in a single operation. + const writer = port.writable.getWriter(); + const data = new Uint8Array(1024); + const promise = writer.write(data); + writer.releaseLock(); + + // Read half of the written data and then detach the buffer. + await fakePort.readable(); + await fakePort.readWithLength(data.byteLength / 2); + detachBuffer(data.buffer); + + // When the buffer is detached its length becomes zero and so the write should + // succeed but it is undefined how much data was written before that happened. + await promise; + + await port.close(); +}, 'Detaching a buffer while writing is safe'); diff --git a/testing/web-platform/tests/serial/serial_getPorts.https.any.js b/testing/web-platform/tests/serial/serial_getPorts.https.any.js new file mode 100644 index 0000000000..03ab5f7e26 --- /dev/null +++ b/testing/web-platform/tests/serial/serial_getPorts.https.any.js @@ -0,0 +1,25 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + fake.addPort(); + fake.addPort(); + + let ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 2); + assert_true(ports[0] instanceof SerialPort); + assert_true(ports[1] instanceof SerialPort); +}, 'getPorts() returns the set of configured fake ports'); + +serial_test(async (t, fake) => { + fake.addPort(); + + let portsFirst = await navigator.serial.getPorts(); + assert_equals(portsFirst.length, 1, 'first call returns one port'); + assert_true(portsFirst[0] instanceof SerialPort); + let portsSecond = await navigator.serial.getPorts(); + assert_equals(portsSecond.length, 1, 'second call returns one port'); + assert_true(portsSecond[0] instanceof SerialPort); + assert_true(portsFirst[0] === portsSecond[0]); +}, 'getPorts() returns the same port objects every time'); diff --git a/testing/web-platform/tests/serial/serial_onconnect.https.any.js b/testing/web-platform/tests/serial/serial_onconnect.https.any.js new file mode 100644 index 0000000000..e2ff9a0f0a --- /dev/null +++ b/testing/web-platform/tests/serial/serial_onconnect.https.any.js @@ -0,0 +1,29 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const eventWatcher = + new EventWatcher(t, navigator.serial, ['connect', 'disconnect']); + + // Wait for getPorts() to resolve in order to ensure that the Mojo client + // interface has been configured. + let ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 0); + + fake.addPort(); + const event1 = await eventWatcher.wait_for(['connect']); + assert_true(event1 instanceof Event); + assert_true(event1.target instanceof SerialPort); + + fake.addPort(); + const event2 = await eventWatcher.wait_for(['connect']); + assert_true(event2 instanceof Event); + assert_true(event2.target instanceof SerialPort); + assert_not_equals(event1.target, event2.target); + + ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 2); + assert_in_array(event1.target, ports); + assert_in_array(event2.target, ports); +}, 'A "connect" event is fired when ports are added.'); diff --git a/testing/web-platform/tests/serial/serial_ondisconnect.https.any.js b/testing/web-platform/tests/serial/serial_ondisconnect.https.any.js new file mode 100644 index 0000000000..583a9ea52c --- /dev/null +++ b/testing/web-platform/tests/serial/serial_ondisconnect.https.any.js @@ -0,0 +1,37 @@ +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test(async (t, fake) => { + const eventWatcher = + new EventWatcher(t, navigator.serial, ['connect', 'disconnect']); + + // Wait for getPorts() to resolve in order to ensure that the Mojo client + // interface has been configured. + let ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 0); + + // Add ports one at a time so that we can map tokens to ports. + const token1 = fake.addPort(); + const port1 = (await eventWatcher.wait_for(['connect'])).target; + + const token2 = fake.addPort(); + const port2 = (await eventWatcher.wait_for(['connect'])).target; + + fake.removePort(token2); + const event1 = await eventWatcher.wait_for(['disconnect']); + assert_true(event1 instanceof Event); + assert_equals(event1.target, port2); + + ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 1); + assert_equals(ports[0], port1); + + fake.removePort(token1); + const event2 = await eventWatcher.wait_for(['disconnect']); + assert_true(event2 instanceof Event); + assert_equals(event2.target, port1); + + ports = await navigator.serial.getPorts(); + assert_equals(ports.length, 0); +}, 'A "disconnect" event is fired when ports are added.'); diff --git a/testing/web-platform/tests/serial/serial_requestPort.https.window.js b/testing/web-platform/tests/serial/serial_requestPort.https.window.js new file mode 100644 index 0000000000..4edcfa6a46 --- /dev/null +++ b/testing/web-platform/tests/serial/serial_requestPort.https.window.js @@ -0,0 +1,66 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/resources/test-only-api.js +// META: script=/serial/resources/common.js +// META: script=resources/automation.js + +serial_test((t, fake) => { + return promise_rejects_dom( + t, 'SecurityError', navigator.serial.requestPort()); +}, 'requestPort() rejects without a user gesture'); + +serial_test(async (t, fake) => { + await trustedClick(); + return promise_rejects_dom( + t, 'NotFoundError', navigator.serial.requestPort()); +}, 'requestPort() rejects if no port has been selected'); + +serial_test(async (t, fake) => { + let token = fake.addPort(); + fake.setSelectedPort(token); + + await trustedClick(); + let port = await navigator.serial.requestPort(); + assert_true(port instanceof SerialPort); +}, 'requestPort() returns the selected port'); + +serial_test(async (t, fake) => { + let token = fake.addPort(); + fake.setSelectedPort(token); + + await trustedClick(); + let firstPort = await navigator.serial.requestPort(); + assert_true(firstPort instanceof SerialPort); + let secondPort = await navigator.serial.requestPort(); + assert_true(secondPort instanceof SerialPort); + assert_true(firstPort === secondPort); +}, 'requestPort() returns the same port object every time'); + +serial_test(async (t, fake) => { + let token = fake.addPort(); + fake.setSelectedPort(token); + + await trustedClick(); + let port = await navigator.serial.requestPort({filters: []}); + assert_true(port instanceof SerialPort); +}, 'An empty list of filters is valid'); + +serial_test(async (t, fake) => { + let token = fake.addPort(); + fake.setSelectedPort(token); + + await trustedClick(); + return promise_rejects_js(t, TypeError, navigator.serial.requestPort({ + filters: [{}], + })); +}, 'An empty filter is not valid'); + +serial_test(async (t, fake) => { + let token = fake.addPort(); + fake.setSelectedPort(token); + + await trustedClick(); + return promise_rejects_js(t, TypeError, navigator.serial.requestPort({ + filters: [{usbProductId: 0x0001}], + })); +}, 'requestPort() requires a USB vendor ID if a product ID specified'); |