serial: Export manual tests into Web Platform Tests
These tests don't have any automation dependencies (since they are
manual) so can be exported first.
Bug: 884928
Change-Id: I6a3b9240d7ce8d61bfea43e5445d1334725bbd64
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2572745
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Joshua Bell <jsbell@chromium.org>
Auto-Submit: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Joshua Bell <jsbell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833542}
diff --git a/serial/resources/common.js b/serial/resources/common.js
new file mode 100644
index 0000000..5177f83
--- /dev/null
+++ b/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/serial/resources/manual.js b/serial/resources/manual.js
new file mode 100644
index 0000000..4ac46b6
--- /dev/null
+++ b/serial/resources/manual.js
@@ -0,0 +1,38 @@
+let manualTestPort = null;
+
+navigator.serial.addEventListener('disconnect', (e) => {
+ if (e.port === 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_loopback_serial_test(func, name, properties) {
+ promise_test(async (test) => {
+ await func(test, await getPortForManualTest());
+ }, name, properties);
+}
diff --git a/serial/serialPort_disconnect-manual.https.html b/serial/serialPort_disconnect-manual.https.html
new file mode 100644
index 0000000..3a2e134
--- /dev/null
+++ b/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_loopback_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_loopback_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/serial/serialPort_loopback-manual.https.html b/serial/serialPort_loopback-manual.https.html
new file mode 100644
index 0000000..9e7801d
--- /dev/null
+++ b/serial/serialPort_loopback-manual.https.html
@@ -0,0 +1,181 @@
+<!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_loopback_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_loopback_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_loopback_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.');
+
+ manual_loopback_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.');
+
+ manual_loopback_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>