diff options
Diffstat (limited to 'testing/web-platform/tests/serial')
27 files changed, 926 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..019eebd06b --- /dev/null +++ b/testing/web-platform/tests/serial/README.md @@ -0,0 +1,10 @@ +# Web Serial Testing + +Currently Web Serial only provide permission policy and maualy tests. The goal in future is to define test API specification similar to [WebUSB] and provide test-only interface to support more comprehensive tests. + +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. + +[WebUSB]: webusb +[Web Serial API]: https://wicg.github.io/serial diff --git a/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html new file mode 100644 index 0000000000..b2f630a319 --- /dev/null +++ b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + 'use strict'; + + promise_test(async (t) => { + await promise_rejects_dom( + t, 'SecurityError', navigator.serial.getPorts(), + 'getPorts() should throw a SecurityError DOMException when called ' + + 'from a context where the top-level document has an opaque origin.'); + }, 'Calls to Serial APIs from an origin with opaque top origin get blocked.'); +</script> diff --git a/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html.headers new file mode 100644 index 0000000000..1efcf8c226 --- /dev/null +++ b/testing/web-platform/tests/serial/getPorts/reject_opaque_origin.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts diff --git a/testing/web-platform/tests/serial/getPorts/sandboxed_iframe.https.window.js b/testing/web-platform/tests/serial/getPorts/sandboxed_iframe.https.window.js new file mode 100644 index 0000000000..0a99f75aae --- /dev/null +++ b/testing/web-platform/tests/serial/getPorts/sandboxed_iframe.https.window.js @@ -0,0 +1,20 @@ +'use strict'; + +promise_test(async (t) => { + let iframe = document.createElement('iframe'); + await new Promise(resolve => { + iframe.src = '../resources/open-in-iframe.html'; + iframe.sandbox.add('allow-scripts'); + iframe.allow = 'serial'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + + await new Promise(resolve => { + window.addEventListener('message', t.step_func(messageEvent => { + assert_equals(messageEvent.data, 'Success'); + resolve(); + })); + iframe.contentWindow.postMessage({type: 'GetPorts'}, '*'); + }); +}, 'GetPorts from a sandboxed iframe is valid.'); 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/requestPort/reject_opaque_origin.https.html b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html new file mode 100644 index 0000000000..ade8ae7392 --- /dev/null +++ b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + 'use strict'; + + promise_test(async (t) => { + await promise_rejects_dom( + t, 'SecurityError', navigator.serial.requestPort(), + 'requestPort() should throw a SecurityError DOMException when called ' + + 'from a context where the top-level document has an opaque origin.'); + }, 'Calls to Serial APIs from an origin with opaque top origin get blocked.'); +</script> diff --git a/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html.headers new file mode 100644 index 0000000000..1efcf8c226 --- /dev/null +++ b/testing/web-platform/tests/serial/requestPort/reject_opaque_origin.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts diff --git a/testing/web-platform/tests/serial/requestPort/sandboxed_iframe.https.window.js b/testing/web-platform/tests/serial/requestPort/sandboxed_iframe.https.window.js new file mode 100644 index 0000000000..f6d7abad10 --- /dev/null +++ b/testing/web-platform/tests/serial/requestPort/sandboxed_iframe.https.window.js @@ -0,0 +1,24 @@ +'use strict'; + +promise_test(async (t) => { + let iframe = document.createElement('iframe'); + await new Promise(resolve => { + iframe.src = '../resources/open-in-iframe.html'; + iframe.sandbox.add('allow-scripts'); + iframe.allow = 'serial'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + + await new Promise(resolve => { + window.addEventListener('message', t.step_func(messageEvent => { + // The failure message of no device chosen is expected. The point here is + // to validate not failing because of a sandboxed iframe. + assert_equals( + 'FAIL: NotFoundError: Failed to execute \'requestPort\' on \'Serial\': No port selected by the user.', + messageEvent.data); + resolve(); + })); + iframe.contentWindow.postMessage({type: 'RequestPort'}, '*'); + }); +}, 'RequestPort from a sandboxed iframe is valid.'); 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/open-in-iframe.html b/testing/web-platform/tests/serial/resources/open-in-iframe.html new file mode 100644 index 0000000000..9bf8beb66a --- /dev/null +++ b/testing/web-platform/tests/serial/resources/open-in-iframe.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> + +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + <button>Fake user gesture</button> +</body> + +<script> + 'use strict'; + + test_driver.set_test_context(parent); + + window.onmessage = messageEvent => { + switch (messageEvent.data.type) { + case 'GetPorts': + navigator.serial.getPorts() + .then(ports => parent.postMessage('Success', '*')) + .catch(err => parent.postMessage(`FAIL: ${err}`, '*')); + break; + case 'RequestPort': + test_driver.click(document.getElementsByTagName('button')[0]) + .then(() => navigator.serial.requestPort({filters: []})) + .then(port => parent.postMessage('Success', '*')) + .catch(err => parent.postMessage(`FAIL: ${err}`, '*')); + break; + default: + parent.postMessage( + `FAIL: Bad message type: ${messageEvent.data}`, '*'); + }; + }; +</script> 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..d482b19cae --- /dev/null +++ b/testing/web-platform/tests/serial/serial-disabled-by-permissions-policy.https.sub.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/test-only-api.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_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_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_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> |