1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * Unit tests for the JS serial service client.
8 * These test that configuration and data are correctly transmitted between the
9 * client and the service. They are launched by
10 * extensions/renderer/api/serial/serial_api_unittest.cc.
13 var test = require('test').binding;
14 var serial = require('serial').binding;
15 var unittestBindings = require('test_environment_specific_bindings');
16 var utils = require('utils');
18 var timeoutManager = new unittestBindings.TimeoutManager();
19 timeoutManager.installGlobals();
23 var connectionId = null;
25 var OPTIONS_VALUES = [
26 {}, // SetPortOptions is called once during connection.
35 {ctsFlowControl: false},
36 {ctsFlowControl: true},
44 // Create a serial connection. That serial connection will be used by the other
45 // helper functions below.
46 function connect(options) {
47 options = options || {
48 name: 'test connection',
49 bufferSize: BUFFER_SIZE,
50 receiveTimeout: 12345,
54 return utils.promise(serial.connect, 'device', options).then(function(info) {
55 connectionId = info.connectionId;
60 // Serialize and deserialize all serial connections, preserving onData and
61 // onError event listeners.
62 function serializeRoundTrip() {
63 return requireAsync('serial_service').then(function(serialService) {
64 function serializeConnections(connections) {
65 var serializedConnections = [];
66 for (var connection of connections.values()) {
67 serializedConnections.push(serializeConnection(connection));
69 return Promise.all(serializedConnections);
72 function serializeConnection(connection) {
73 var onData = connection.onData;
74 var onError = connection.onError;
75 return connection.serialize().then(function(serialization) {
77 serialization: serialization,
84 function deserializeConnections(serializedConnections) {
85 $Array.forEach(serializedConnections, function(serializedConnection) {
86 var connection = serialService.Connection.deserialize(
87 serializedConnection.serialization);
88 connection.onData = serializedConnection.onData;
89 connection.onError = serializedConnection.onError;
90 connection.resumeReceives();
94 return serialService.getConnections()
95 .then(serializeConnections)
96 .then(deserializeConnections);
100 // Returns a promise that will resolve to the connection info for the
103 return utils.promise(serial.getInfo, connectionId);
106 // Returns a function that checks that the values of keys contained within
107 // |expectedInfo| match the values of the same keys contained within |info|.
108 function checkInfo(expectedInfo) {
109 return function(info) {
110 for (var key in expectedInfo) {
111 test.assertEq(expectedInfo[key], info[key]);
116 // Returns a function that will update the options of the serial connection with
117 // those contained within |values|.
118 function update(values) {
120 return utils.promise(serial.update, connectionId, values);
124 // Checks that the previous operation succeeded.
125 function expectSuccess(success) {
126 test.assertTrue(success);
129 // Returns a function that checks that the send result matches |bytesSent| and
130 // |error|. If no error is expected, |error| may be omitted.
131 function expectSendResult(bytesSent, error) {
132 return function(sendInfo) {
133 test.assertEq(bytesSent, sendInfo.bytesSent);
134 test.assertEq(error, sendInfo.error);
138 // Returns a function that checks that the current time is |expectedTime|.
139 function expectCurrentTime(expectedTime) {
141 test.assertEq(expectedTime, timeoutManager.currentTime);
145 // Returns a promise that will resolve to the device control signals for the
146 // serial connection.
147 function getControlSignals() {
148 return utils.promise(serial.getControlSignals, connectionId);
151 // Returns a function that will set the control signals for the serial
152 // connection to |signals|.
153 function setControlSignals(signals) {
155 return utils.promise(serial.setControlSignals, connectionId, signals);
159 // Returns a function that will set the paused state of the serial connection to
161 function setPaused(paused) {
163 return utils.promise(serial.setPaused, connectionId, paused);
167 // Sets a function to be called once when data is received. Returns a promise
168 // that will resolve once the hook is installed.
169 function addReceiveHook(callback) {
170 return requireAsync('serial_service').then(function(serialService) {
172 var dataReceived = serialService.Connection.prototype.onDataReceived_;
173 serialService.Connection.prototype.onDataReceived_ = function() {
174 var result = $Function.apply(dataReceived, this, arguments);
183 // Sets a function to be called once when a receive error is received. Returns a
184 // promise that will resolve once the hook is installed.
185 function addReceiveErrorHook(callback) {
186 return requireAsync('serial_service').then(function(serialService) {
188 var receiveError = serialService.Connection.prototype.onReceiveError_;
189 serialService.Connection.prototype.onReceiveError_ = function() {
190 var result = $Function.apply(receiveError, this, arguments);
199 function listenOnce(targetEvent) {
200 return new Promise(function(resolve, reject) {
201 targetEvent.addListener(function(result) {
207 function disconnect() {
208 return utils.promise(serial.disconnect, connectionId).then(function(success) {
209 test.assertTrue(success);
214 function checkClientConnectionInfo(connectionInfo) {
215 test.assertTrue(connectionInfo.persistent);
216 test.assertEq('test connection', connectionInfo.name);
217 test.assertEq(12345, connectionInfo.receiveTimeout);
218 test.assertEq(6789, connectionInfo.sendTimeout);
219 test.assertEq(BUFFER_SIZE, connectionInfo.bufferSize);
220 test.assertFalse(connectionInfo.paused);
223 function checkServiceConnectionInfo(connectionInfo) {
224 test.assertEq(9600, connectionInfo.bitrate);
225 test.assertEq('eight', connectionInfo.dataBits);
226 test.assertEq('no', connectionInfo.parityBit);
227 test.assertEq('one', connectionInfo.stopBits);
228 test.assertFalse(connectionInfo.ctsFlowControl);
231 function checkConnectionInfo(connectionInfo) {
232 checkClientConnectionInfo(connectionInfo);
233 checkServiceConnectionInfo(connectionInfo);
234 test.assertEq(12, $Object.keys(connectionInfo).length);
237 function sendData() {
239 var buffer = new ArrayBuffer(data.length);
240 var byteBuffer = new Int8Array(buffer);
241 for (var i = 0; i < data.length; i++) {
242 byteBuffer[i] = data.charCodeAt(i);
244 return utils.promise(serial.send, connectionId, buffer);
247 function checkReceivedData(result) {
249 test.assertEq(connectionId, result.connectionId);
250 test.assertEq(data.length, result.data.byteLength);
251 var resultByteBuffer = new Int8Array(result.data);
252 for (var i = 0; i < data.length; i++) {
253 test.assertEq(data.charCodeAt(i), resultByteBuffer[i]);
257 function checkReceiveError(expectedError) {
258 return function(result) {
259 test.assertEq(connectionId, result.connectionId);
260 test.assertEq(expectedError, result.error);
264 function runReceiveErrorTest(expectedError) {
265 var errorReceived = listenOnce(serial.onReceiveError);
269 .then(checkReceiveError(expectedError)),
272 .then(checkInfo({paused: true})),
275 .then(test.succeed, test.fail);
278 function runSendErrorTest(expectedError) {
281 .then(expectSendResult(0, expectedError))
283 .then(test.succeed, test.fail);
286 unittestBindings.exportTests([
287 // Test that getDevices correctly transforms the data returned by the
288 // SerialDeviceEnumerator.
289 function testGetDevices() {
290 utils.promise(serial.getDevices).then(function(devices) {
291 test.assertEq(3, devices.length);
292 test.assertEq(4, $Object.keys(devices[0]).length);
293 test.assertEq('device', devices[0].path);
294 test.assertEq(1234, devices[0].vendorId);
295 test.assertEq(5678, devices[0].productId);
296 test.assertEq('foo', devices[0].displayName);
297 test.assertEq(1, $Object.keys(devices[1]).length);
298 test.assertEq('another_device', devices[1].path);
299 test.assertEq(1, $Object.keys(devices[2]).length);
300 test.assertEq('', devices[2].path);
301 }).then(test.succeed, test.fail);
304 // Test that the correct error message is returned when an error occurs in
305 // connecting to the port. This test uses an IoHandler that fails to connect.
306 function testConnectFail() {
307 serial.connect('device',
308 test.callbackFail('Failed to connect to the port.'));
311 // Test that the correct error message is returned when an error occurs in
312 // calling getPortInfo after connecting to the port. This test uses an
313 // IoHandler that fails on calls to GetPortInfo.
314 function testGetInfoFailOnConnect() {
315 serial.connect('device',
316 test.callbackFail('Failed to connect to the port.'));
319 // Test that the correct error message is returned when an invalid bit-rate
320 // value is passed to connect.
321 function testConnectInvalidBitrate() {
322 serial.connect('device', {bitrate: -1}, test.callbackFail(
323 'Failed to connect to the port.'));
326 // Test that a successful connect returns the expected connection info.
327 function testConnect() {
329 .then(checkConnectionInfo)
331 .then(test.succeed, test.fail);
334 // Test that a connection created with no options has the correct default
336 function testConnectDefaultOptions() {
337 connect({}).then(function(connectionInfo) {
338 test.assertEq(9600, connectionInfo.bitrate);
339 test.assertEq('eight', connectionInfo.dataBits);
340 test.assertEq('no', connectionInfo.parityBit);
341 test.assertEq('one', connectionInfo.stopBits);
342 test.assertFalse(connectionInfo.ctsFlowControl);
343 test.assertFalse(connectionInfo.persistent);
344 test.assertEq('', connectionInfo.name);
345 test.assertEq(0, connectionInfo.receiveTimeout);
346 test.assertEq(0, connectionInfo.sendTimeout);
347 test.assertEq(4096, connectionInfo.bufferSize);
350 .then(test.succeed, test.fail);
353 // Test that a getInfo call correctly converts the service-side info from the
354 // Mojo format and returns both it and the client-side configuration.
355 function testGetInfo() {
358 .then(checkConnectionInfo)
360 .then(test.succeed, test.fail);
363 // Test that a getInfo call returns the correct info after serialization.
364 function testGetInfoAfterSerialization() {
366 .then(serializeRoundTrip)
368 .then(checkConnectionInfo)
370 .then(test.succeed, test.fail);
373 // Test that only client-side options are returned when the service fails a
374 // getInfo call. This test uses an IoHandler that fails GetPortInfo calls
375 // after the initial call during connect.
376 function testGetInfoFailToGetPortInfo() {
377 var info = connect().then(getInfo);
379 info.then(function(connectionInfo) {
380 test.assertFalse('bitrate' in connectionInfo);
381 test.assertFalse('dataBits' in connectionInfo);
382 test.assertFalse('parityBit' in connectionInfo);
383 test.assertFalse('stopBit' in connectionInfo);
384 test.assertFalse('ctsFlowControl' in connectionInfo);
386 info.then(checkClientConnectionInfo),
389 .then(test.succeed, test.fail);
392 // Test that getConnections returns an array containing the open connection.
393 function testGetConnections() {
394 connect().then(function() {
395 return utils.promise(serial.getConnections);
396 }).then(function(connections) {
397 test.assertEq(1, connections.length);
398 checkConnectionInfo(connections[0]);
401 .then(test.succeed, test.fail);
404 // Test that getControlSignals correctly converts the Mojo format result. This
405 // test uses an IoHandler that returns values matching the pattern being
407 function testGetControlSignals() {
408 function checkControlSignals(expectedBitfield) {
409 return function(signals) {
410 test.assertEq(!!(expectedBitfield & 1), signals.dcd);
411 test.assertEq(!!(expectedBitfield & 2), signals.cts);
412 test.assertEq(!!(expectedBitfield & 4), signals.ri);
413 test.assertEq(!!(expectedBitfield & 8), signals.dsr);
416 var promiseChain = connect();
417 for (var i = 0; i < 16; i++) {
418 promiseChain = promiseChain
419 .then(getControlSignals)
420 .then(checkControlSignals(i));
424 .then(test.succeed, test.fail);
427 // Test that setControlSignals correctly converts to the Mojo format result.
428 // This test uses an IoHandler that returns values following the same table of
429 // values as |signalsValues|.
430 function testSetControlSignals() {
431 var signalsValues = [
436 {dtr: false, rts: false},
437 {dtr: true, rts: false},
439 {dtr: false, rts: true},
440 {dtr: true, rts: true},
442 var promiseChain = connect();
443 for (var i = 0; i < signalsValues.length; i++) {
444 promiseChain = promiseChain.then(setControlSignals(signalsValues[i]));
448 .then(test.succeed, test.fail);
451 // Test that update correctly passes values to the service only for
452 // service-side options and that all update calls are reflected by the result
453 // of getInfo calls. This test uses an IoHandler that expects corresponding
454 // ConfigurePort calls.
455 function testUpdate() {
456 var promiseChain = connect()
458 .then(checkInfo(OPTIONS_VALUES[i]));
459 for (var i = 1; i < OPTIONS_VALUES.length; i++) {
460 promiseChain = promiseChain
461 .then(update(OPTIONS_VALUES[i]))
464 .then(checkInfo(OPTIONS_VALUES[i]));
468 .then(test.succeed, test.fail);
471 // Test that options set by update persist after serialization.
472 function testUpdateAcrossSerialization() {
473 var promiseChain = connect()
474 .then(serializeRoundTrip)
476 .then(checkInfo(OPTIONS_VALUES[i]));
477 for (var i = 1; i < OPTIONS_VALUES.length; i++) {
478 promiseChain = promiseChain
479 .then(update(OPTIONS_VALUES[i]))
481 .then(serializeRoundTrip)
483 .then(checkInfo(OPTIONS_VALUES[i]));
487 .then(test.succeed, test.fail);
490 // Test that passing an invalid bit-rate reslts in an error.
491 function testUpdateInvalidBitrate() {
493 .then(update({bitrate: -1}))
494 .then(function(success) {
495 test.assertFalse(success);
498 .then(test.succeed, test.fail);
501 // Test flush. This test uses an IoHandler that counts the number of flush
503 function testFlush() {
504 connect().then(function() {
505 return utils.promise(serial.flush, connectionId);
509 .then(test.succeed, test.fail);
512 // Test that setPaused values are reflected by the results returned by getInfo
514 function testSetPaused() {
516 .then(setPaused(true))
518 .then(checkInfo({paused: true}))
519 .then(setPaused(false))
521 .then(checkInfo({paused: false}))
523 .then(test.succeed, test.fail);
526 // Test that a send and a receive correctly echoes data. This uses an
527 // IoHandler that echoes data sent to it.
528 function testEcho() {
532 .then(expectSendResult(4)),
533 listenOnce(serial.onReceive)
534 .then(checkReceivedData),
537 .then(test.succeed, test.fail);
540 // Test that a send while another send is in progress returns a pending error.
541 function testSendDuringExistingSend() {
542 var connected = connect();
546 .then(expectSendResult(4)),
549 .then(expectSendResult(0, 'pending')),
552 .then(test.succeed, test.fail);
555 // Test that a second send after the first finishes is successful. This uses
556 // an IoHandler that echoes data sent to it.
557 function testSendAfterSuccessfulSend() {
560 .then(expectSendResult(4))
562 .then(expectSendResult(4))
564 .then(test.succeed, test.fail);
567 // Test that a second send after the first fails is successful. This uses an
568 // IoHandler that returns system_error for only the first send.
569 function testSendPartialSuccessWithError() {
572 .then(expectSendResult(2, 'system_error'))
574 .then(expectSendResult(4))
576 .then(test.succeed, test.fail);
579 // Test that a send and a receive correctly echoes data after serialization.
580 function testEchoAfterSerialization() {
583 .then(serializeRoundTrip)
585 .then(expectSendResult(4)),
586 listenOnce(serial.onReceive).then(checkReceivedData)
589 .then(test.succeed, test.fail);
592 // Test that a timed-out send returns a timeout error and that changing the
593 // send timeout during a send does not affect its timeout. This test uses an
594 // IoHandle that never completes sends.
595 function testSendTimeout() {
596 var connected = connect({sendTimeout: 5});
597 var sent = connected.then(sendData);
599 sent.then(expectSendResult(0, 'timeout')),
600 sent.then(expectCurrentTime(5)),
601 connected.then(update({sendTimeout: 10}))
603 .then(timeoutManager.run.bind(timeoutManager, 1)),
606 .then(test.succeed, test.fail);
609 // Test that send timeouts still function correctly after a serialization
611 function testSendTimeoutAfterSerialization() {
612 var connected = connect({sendTimeout: 5}).then(serializeRoundTrip);
613 var sent = connected.then(sendData);
615 sent.then(expectSendResult(0, 'timeout')),
616 sent.then(expectCurrentTime(5)),
617 connected.then(update({sendTimeout: 10}))
619 .then(timeoutManager.run.bind(timeoutManager, 1)),
622 .then(test.succeed, test.fail);
625 // Test that a timed-out send returns a timeout error and that disabling the
626 // send timeout during a send does not affect its timeout. This test uses an
627 // IoHandle that never completes sends.
628 function testDisableSendTimeout() {
629 var connected = connect({sendTimeout: 5});
630 var sent = connected.then(sendData);
632 sent.then(expectSendResult(0, 'timeout')),
633 sent.then(expectCurrentTime(5)),
634 connected.then(update({sendTimeout: 0}))
636 .then(timeoutManager.run.bind(timeoutManager, 1)),
639 .then(test.succeed, test.fail);
642 // Test that data received while the connection is paused is queued and
643 // dispatched once the connection is unpaused.
644 function testPausedReceive() {
646 // Wait until the receive hook is installed, then start the test.
647 addReceiveHook(function() {
648 // Unpause the connection after the connection has queued the received
649 // data to ensure the queued data is dispatched when the connection is
652 utils.promise(serial.setPaused, connectionId, false),
653 // Check that setPaused(false) is idempotent.
654 utils.promise(serial.setPaused, connectionId, false),
659 // Check that setPaused(true) is idempotent.
661 utils.promise(serial.setPaused, connectionId, true),
662 utils.promise(serial.setPaused, connectionId, true),
665 listenOnce(serial.onReceive).then(checkReceivedData),
668 .then(test.succeed, test.fail);
671 // Test that a receive error received while the connection is paused is queued
672 // and dispatched once the connection is unpaused.
673 function testPausedReceiveError() {
675 // Wait until the receive hook is installed, then start the test.
676 addReceiveErrorHook(function() {
677 // Unpause the connection after the connection has queued the received
678 // data to ensure the queued data is dispatched when the connection is
680 utils.promise(serial.setPaused, connectionId, false).catch(test.fail);
683 .then(setPaused(true)),
684 listenOnce(serial.onReceiveError)
685 .then(checkReceiveError('device_lost')),
688 .then(test.succeed, test.fail);
689 serial.onReceive.addListener(function() {
690 test.fail('unexpected onReceive event');
694 // Test that receive timeouts trigger after the timeout time elapses and that
695 // changing the receive timeout does not affect a wait in progress.
696 function testReceiveTimeout() {
697 var errorReceived = listenOnce(serial.onReceiveError);
699 errorReceived.then(checkReceiveError('timeout')),
700 errorReceived.then(expectCurrentTime(20)),
703 .then(checkInfo({paused: false})),
704 connect({receiveTimeout: 20})
705 // Changing the timeout does not take effect until the current
706 // timeout expires or a receive completes.
707 .then(update({receiveTimeout: 10}))
709 .then(timeoutManager.run.bind(timeoutManager, 1)),
712 .then(test.succeed, test.fail);
715 // Test that receive timeouts still function correctly after a serialization
717 function testReceiveTimeoutAfterSerialization() {
718 var errorReceived = listenOnce(serial.onReceiveError);
720 errorReceived.then(checkReceiveError('timeout')),
721 errorReceived.then(expectCurrentTime(20)),
724 .then(checkInfo({paused: false})),
725 connect({receiveTimeout: 20})
726 .then(serializeRoundTrip)
727 .then(timeoutManager.run.bind(timeoutManager, 1)),
730 .then(test.succeed, test.fail);
733 // Test that receive timeouts trigger after the timeout time elapses and that
734 // disabling the receive timeout does not affect a wait in progress.
735 function testDisableReceiveTimeout() {
736 var errorReceived = listenOnce(serial.onReceiveError);
738 errorReceived.then(checkReceiveError('timeout')),
739 errorReceived.then(expectCurrentTime(20)),
742 .then(checkInfo({paused: false})),
743 connect({receiveTimeout: 20})
744 // Disabling the timeout does not take effect until the current
745 // timeout expires or a receive completes.
746 .then(update({receiveTimeout: 0}))
748 .then(timeoutManager.run.bind(timeoutManager, 1)),
751 .then(test.succeed, test.fail);
754 // Test that a receive error from the service is correctly dispatched. This
755 // test uses an IoHandler that only reports 'disconnected' receive errors.
756 function testReceiveErrorDisconnected() {
757 runReceiveErrorTest('disconnected');
760 // Test that a receive error from the service is correctly dispatched. This
761 // test uses an IoHandler that only reports 'device_lost' receive errors.
762 function testReceiveErrorDeviceLost() {
763 runReceiveErrorTest('device_lost');
766 // Test that a receive from error the service is correctly dispatched. This
767 // test uses an IoHandler that only reports 'system_error' receive errors.
768 function testReceiveErrorSystemError() {
769 runReceiveErrorTest('system_error');
772 // Test that a send error from the service is correctly returned as the send
773 // result. This test uses an IoHandler that only reports 'disconnected' send
775 function testSendErrorDisconnected() {
776 runSendErrorTest('disconnected');
779 // Test that a send error from the service is correctly returned as the send
780 // result. This test uses an IoHandler that only reports 'system_error' send
782 function testSendErrorSystemError() {
783 runSendErrorTest('system_error');
786 // Test that disconnect returns the correct error for a connection ID that
788 function testDisconnectUnknownConnectionId() {
789 serial.disconnect(-1, test.callbackFail('Serial connection not found.'));
792 // Test that getInfo returns the correct error for a connection ID that does
794 function testGetInfoUnknownConnectionId() {
795 serial.getInfo(-1, test.callbackFail('Serial connection not found.'));
798 // Test that update returns the correct error for a connection ID that does
800 function testUpdateUnknownConnectionId() {
801 serial.update(-1, {}, test.callbackFail('Serial connection not found.'));
804 // Test that setControlSignals returns the correct error for a connection ID
805 // that does not exist.
806 function testSetControlSignalsUnknownConnectionId() {
807 serial.setControlSignals(-1, {}, test.callbackFail(
808 'Serial connection not found.'));
811 // Test that getControlSignals returns the correct error for a connection ID
812 // that does not exist.
813 function testGetControlSignalsUnknownConnectionId() {
814 serial.getControlSignals(-1, test.callbackFail(
815 'Serial connection not found.'));
818 // Test that flush returns the correct error for a connection ID that does not
820 function testFlushUnknownConnectionId() {
821 serial.flush(-1, test.callbackFail('Serial connection not found.'));
824 // Test that setPaused returns the correct error for a connection ID that does
826 function testSetPausedUnknownConnectionId() {
828 -1, true, test.callbackFail('Serial connection not found.'));
830 -1, false, test.callbackFail('Serial connection not found.'));
833 // Test that send returns the correct error for a connection ID that does not
835 function testSendUnknownConnectionId() {
836 var buffer = new ArrayBuffer(1);
837 serial.send(-1, buffer, test.callbackFail('Serial connection not found.'));
840 function testSendAndStash() {
842 .then(setPaused(true))
844 .then(expectSendResult(4))
845 .then(test.succeed, test.fail);
848 function testRestoreAndReceive() {
851 utils.promise(serial.setPaused, connectionId, false),
852 listenOnce(serial.onReceive).then(checkReceivedData),
855 .then(test.succeed, test.fail);
858 function testRestoreAndReceiveErrorSetUp() {
859 connect().then(test.succeed, test.fail);
862 function testRestoreAndReceiveError() {
865 utils.promise(serial.setPaused, connectionId, false),
866 listenOnce(serial.onReceiveError)
867 .then(checkReceiveError('device_lost')),
870 .then(test.succeed, test.fail);
873 function testStashNoConnections() {
874 connect({persistent: false}).then(test.succeed, test.fail);
877 function testRestoreNoConnections() {
879 .then(function(connectionInfo) {
880 test.assertEq(0, connectionInfo.connectionId);
881 return connectionInfo;
883 .then(checkConnectionInfo)
885 .then(test.succeed, test.fail);
888 ], test.runTests, exports);