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. 4 5 /** 6 * Unit tests for the JS serial service client. 7 * 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. 11 */ 12 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'); 17 18 var timeoutManager = new unittestBindings.TimeoutManager(); 19 timeoutManager.installGlobals(); 20 21 var BUFFER_SIZE = 10; 22 23 var connectionId = null; 24 25 function connect(callback, options) { 26 options = options || { 27 name: 'test connection', 28 bufferSize: BUFFER_SIZE, 29 receiveTimeout: 12345, 30 sendTimeout: 6789, 31 persistent: true, 32 }; 33 serial.connect('device', options, test.callbackPass(function(connectionInfo) { 34 connectionId = connectionInfo.connectionId; 35 if (callback) 36 callback(connectionInfo); 37 })); 38 } 39 40 // Sets a function to be called once when data is received. Returns a promise 41 // that will resolve once the hook is installed. 42 function addReceiveHook(callback) { 43 return requireAsync('serial_service').then(function(serialService) { 44 var called = false; 45 var dataReceived = serialService.Connection.prototype.onDataReceived_; 46 serialService.Connection.prototype.onDataReceived_ = function() { 47 var result = $Function.apply(dataReceived, this, arguments); 48 if (!called) 49 callback(); 50 called = true; 51 return result; 52 }; 53 }); 54 } 55 56 // Sets a function to be called once when a receive error is received. Returns a 57 // promise that will resolve once the hook is installed. 58 function addReceiveErrorHook(callback) { 59 return requireAsync('serial_service').then(function(serialService) { 60 var called = false; 61 var receiveError = serialService.Connection.prototype.onReceiveError_; 62 serialService.Connection.prototype.onReceiveError_ = function() { 63 var result = $Function.apply(receiveError, this, arguments); 64 if (!called) 65 callback(); 66 called = true; 67 return result; 68 }; 69 }); 70 } 71 72 function disconnect() { 73 serial.disconnect(connectionId, test.callbackPass(function(success) { 74 test.assertTrue(success); 75 connectionId = null; 76 })); 77 } 78 79 function checkClientConnectionInfo(connectionInfo) { 80 test.assertFalse(connectionInfo.persistent); 81 test.assertEq('test connection', connectionInfo.name); 82 test.assertEq(12345, connectionInfo.receiveTimeout); 83 test.assertEq(6789, connectionInfo.sendTimeout); 84 test.assertEq(BUFFER_SIZE, connectionInfo.bufferSize); 85 test.assertFalse(connectionInfo.paused); 86 } 87 88 function checkServiceConnectionInfo(connectionInfo) { 89 test.assertEq(9600, connectionInfo.bitrate); 90 test.assertEq('eight', connectionInfo.dataBits); 91 test.assertEq('no', connectionInfo.parityBit); 92 test.assertEq('one', connectionInfo.stopBits); 93 test.assertFalse(connectionInfo.ctsFlowControl); 94 } 95 96 function checkConnectionInfo(connectionInfo) { 97 checkClientConnectionInfo(connectionInfo); 98 checkServiceConnectionInfo(connectionInfo); 99 } 100 101 function runReceiveErrorTest(expectedError) { 102 connect(); 103 test.listenOnce(serial.onReceiveError, function(result) { 104 serial.getInfo(connectionId, test.callbackPass(function(connectionInfo) { 105 disconnect(); 106 test.assertTrue(connectionInfo.paused); 107 })); 108 test.assertEq(connectionId, result.connectionId); 109 test.assertEq(expectedError, result.error); 110 }); 111 } 112 113 function runSendErrorTest(expectedError) { 114 connect(function() { 115 var buffer = new ArrayBuffer(1); 116 serial.send(connectionId, buffer, test.callbackPass(function(sendInfo) { 117 disconnect(); 118 test.assertEq(0, sendInfo.bytesSent); 119 test.assertEq(expectedError, sendInfo.error); 120 })); 121 }); 122 } 123 124 function sendData() { 125 var data = 'data'; 126 var buffer = new ArrayBuffer(data.length); 127 var byteBuffer = new Int8Array(buffer); 128 for (var i = 0; i < data.length; i++) { 129 byteBuffer[i] = data.charCodeAt(i); 130 } 131 return utils.promise(serial.send, connectionId, buffer); 132 } 133 134 function checkReceivedData(result) { 135 var data = 'data'; 136 test.assertEq(connectionId, result.connectionId); 137 test.assertEq(data.length, result.data.byteLength); 138 var resultByteBuffer = new Int8Array(result.data); 139 for (var i = 0; i < data.length; i++) { 140 test.assertEq(data.charCodeAt(i), resultByteBuffer[i]); 141 } 142 } 143 144 unittestBindings.exportTests([ 145 // Test that getDevices correctly transforms the data returned by the 146 // SerialDeviceEnumerator. 147 function testGetDevices() { 148 serial.getDevices(test.callbackPass(function(devices) { 149 test.assertEq(3, devices.length); 150 test.assertEq(4, $Object.keys(devices[0]).length); 151 test.assertEq('device', devices[0].path); 152 test.assertEq(1234, devices[0].vendorId); 153 test.assertEq(5678, devices[0].productId); 154 test.assertEq('foo', devices[0].displayName); 155 test.assertEq(1, $Object.keys(devices[1]).length); 156 test.assertEq('another_device', devices[1].path); 157 test.assertEq(1, $Object.keys(devices[2]).length); 158 test.assertEq('', devices[2].path); 159 })); 160 }, 161 162 // Test that the correct error message is returned when an error occurs in 163 // connecting to the port. This test uses an IoHandler that fails to connect. 164 function testConnectFail() { 165 serial.connect('device', 166 test.callbackFail('Failed to connect to the port.')); 167 }, 168 169 // Test that the correct error message is returned when an error occurs in 170 // calling getPortInfo after connecting to the port. This test uses an 171 // IoHandler that fails on calls to GetPortInfo. 172 function testGetInfoFailOnConnect() { 173 serial.connect('device', 174 test.callbackFail('Failed to connect to the port.')); 175 }, 176 177 // Test that the correct error message is returned when an invalid bit-rate 178 // value is passed to connect. 179 function testConnectInvalidBitrate() { 180 serial.connect('device', {bitrate: -1}, test.callbackFail( 181 'Failed to connect to the port.')); 182 }, 183 184 // Test that a successful connect returns the expected connection info. 185 function testConnect() { 186 connect(function(connectionInfo) { 187 disconnect(); 188 checkConnectionInfo(connectionInfo); 189 }); 190 }, 191 192 // Test that a connection created with no options has the correct default 193 // options. 194 function testConnectDefaultOptions() { 195 connect(function(connectionInfo) { 196 disconnect(); 197 test.assertEq(9600, connectionInfo.bitrate); 198 test.assertEq('eight', connectionInfo.dataBits); 199 test.assertEq('no', connectionInfo.parityBit); 200 test.assertEq('one', connectionInfo.stopBits); 201 test.assertFalse(connectionInfo.ctsFlowControl); 202 test.assertFalse(connectionInfo.persistent); 203 test.assertEq('', connectionInfo.name); 204 test.assertEq(0, connectionInfo.receiveTimeout); 205 test.assertEq(0, connectionInfo.sendTimeout); 206 test.assertEq(4096, connectionInfo.bufferSize); 207 }, {}); 208 }, 209 210 // Test that a getInfo call correctly converts the service-side info from the 211 // Mojo format and returns both it and the client-side configuration. 212 function testGetInfo() { 213 connect(function() { 214 serial.getInfo(connectionId, 215 test.callbackPass(function(connectionInfo) { 216 disconnect(); 217 checkConnectionInfo(connectionInfo); 218 })); 219 }); 220 }, 221 222 // Test that only client-side options are returned when the service fails a 223 // getInfo call. This test uses an IoHandler that fails GetPortInfo calls 224 // after the initial call during connect. 225 function testGetInfoFailToGetPortInfo() { 226 connect(function() { 227 serial.getInfo(connectionId, 228 test.callbackPass(function(connectionInfo) { 229 disconnect(); 230 checkClientConnectionInfo(connectionInfo); 231 test.assertFalse('bitrate' in connectionInfo); 232 test.assertFalse('dataBits' in connectionInfo); 233 test.assertFalse('parityBit' in connectionInfo); 234 test.assertFalse('stopBit' in connectionInfo); 235 test.assertFalse('ctsFlowControl' in connectionInfo); 236 })); 237 }); 238 }, 239 240 // Test that getConnections returns an array containing the open connection. 241 function testGetConnections() { 242 connect(function() { 243 serial.getConnections(test.callbackPass(function(connections) { 244 disconnect(); 245 test.assertEq(1, connections.length); 246 checkConnectionInfo(connections[0]); 247 })); 248 }); 249 }, 250 251 // Test that getControlSignals correctly converts the Mojo format result. This 252 // test uses an IoHandler that returns values matching the pattern being 253 // tested. 254 function testGetControlSignals() { 255 connect(function() { 256 var calls = 0; 257 function checkControlSignals(signals) { 258 if (calls == 15) { 259 disconnect(); 260 } else { 261 serial.getControlSignals( 262 connectionId, 263 test.callbackPass(checkControlSignals)); 264 } 265 test.assertEq(!!(calls & 1), signals.dcd); 266 test.assertEq(!!(calls & 2), signals.cts); 267 test.assertEq(!!(calls & 4), signals.ri); 268 test.assertEq(!!(calls & 8), signals.dsr); 269 calls++; 270 } 271 serial.getControlSignals(connectionId, 272 test.callbackPass(checkControlSignals)); 273 }); 274 }, 275 276 // Test that setControlSignals correctly converts to the Mojo format result. 277 // This test uses an IoHandler that returns values following the same table of 278 // values as |signalsValues|. 279 function testSetControlSignals() { 280 connect(function() { 281 var signalsValues = [ 282 {}, 283 {dtr: false}, 284 {dtr: true}, 285 {rts: false}, 286 {dtr: false, rts: false}, 287 {dtr: true, rts: false}, 288 {rts: true}, 289 {dtr: false, rts: true}, 290 {dtr: true, rts: true}, 291 ]; 292 var calls = 0; 293 function setControlSignals(success) { 294 if (calls == signalsValues.length) { 295 disconnect(); 296 } else { 297 serial.setControlSignals(connectionId, 298 signalsValues[calls++], 299 test.callbackPass(setControlSignals)); 300 } 301 test.assertTrue(success); 302 } 303 setControlSignals(true); 304 }); 305 }, 306 307 // Test that update correctly passes values to the service only for 308 // service-side options and that all update calls are reflected by the result 309 // of getInfo calls. This test uses an IoHandler that expects corresponding 310 // ConfigurePort calls. 311 function testUpdate() { 312 connect(function() { 313 var optionsValues = [ 314 {}, // SetPortOptions is called once during connection. 315 {bitrate: 57600}, 316 {dataBits: 'seven'}, 317 {dataBits: 'eight'}, 318 {parityBit: 'no'}, 319 {parityBit: 'odd'}, 320 {parityBit: 'even'}, 321 {stopBits: 'one'}, 322 {stopBits: 'two'}, 323 {ctsFlowControl: false}, 324 {ctsFlowControl: true}, 325 {bufferSize: 1}, 326 {sendTimeout: 0}, 327 {receiveTimeout: 0}, 328 {persistent: false}, 329 {name: 'name'}, 330 ]; 331 var calls = 0; 332 function checkInfo(info) { 333 for (var key in optionsValues[calls]) { 334 test.assertEq(optionsValues[calls][key], info[key]); 335 } 336 setOptions(); 337 } 338 function setOptions() { 339 if (++calls == optionsValues.length) { 340 disconnect(); 341 } else { 342 serial.update(connectionId, 343 optionsValues[calls], 344 test.callbackPass(function(success) { 345 serial.getInfo(connectionId, test.callbackPass(checkInfo)); 346 test.assertTrue(success); 347 })); 348 } 349 } 350 setOptions(); 351 }); 352 }, 353 354 // Test that passing an invalid bit-rate reslts in an error. 355 function testUpdateInvalidBitrate() { 356 connect(function() { 357 serial.update(connectionId, 358 {bitrate: -1}, 359 test.callbackPass(function(success) { 360 disconnect(); 361 test.assertFalse(success); 362 })); 363 }); 364 }, 365 366 // Test flush. This test uses an IoHandler that counts the number of flush 367 // calls. 368 function testFlush() { 369 connect(function() { 370 serial.flush(connectionId, test.callbackPass(function(success) { 371 disconnect(); 372 test.assertTrue(success); 373 })); 374 }); 375 }, 376 377 // Test that setPaused values are reflected by the results returned by getInfo 378 // calls. 379 function testSetPaused() { 380 connect(function() { 381 serial.setPaused(connectionId, true, test.callbackPass(function() { 382 serial.getInfo(connectionId, test.callbackPass(function(info) { 383 serial.setPaused(connectionId, false, test.callbackPass(function() { 384 serial.getInfo(connectionId, test.callbackPass(function(info) { 385 test.assertFalse(info.paused); 386 disconnect(); 387 })); 388 })); 389 test.assertTrue(info.paused); 390 })); 391 })); 392 }); 393 }, 394 395 // Test that a send and a receive correctly echoes data. This uses an 396 // IoHandler that echoes data sent to it. 397 function testEcho() { 398 connect(function() { 399 sendData().then(test.callbackPass(function(sendInfo) { 400 test.assertEq(4, sendInfo.bytesSent); 401 test.assertEq(undefined, sendInfo.error); 402 })); 403 test.listenOnce(serial.onReceive, function(result) { 404 checkReceivedData(result); 405 disconnect(); 406 }); 407 }); 408 }, 409 410 // Test that a send while another send is in progress returns a pending error. 411 function testSendDuringExistingSend() { 412 connect(function() { 413 sendData().then(test.callbackPass(function(sendInfo) { 414 test.assertEq(4, sendInfo.bytesSent); 415 test.assertEq(undefined, sendInfo.error); 416 disconnect(); 417 })); 418 sendData().then(test.callbackPass(function(sendInfo) { 419 test.assertEq(0, sendInfo.bytesSent); 420 test.assertEq('pending', sendInfo.error); 421 })); 422 }); 423 }, 424 425 // Test that a second send after the first finishes is successful. This uses 426 // an IoHandler that echoes data sent to it. 427 function testSendAfterSuccessfulSend() { 428 connect(function() { 429 sendData().then(test.callbackPass(function(sendInfo) { 430 test.assertEq(4, sendInfo.bytesSent); 431 test.assertEq(undefined, sendInfo.error); 432 return sendData(); 433 })).then(test.callbackPass(function(sendInfo) { 434 test.assertEq(4, sendInfo.bytesSent); 435 test.assertEq(undefined, sendInfo.error); 436 })); 437 // Check that the correct data is echoed twice. 438 test.listenOnce(serial.onReceive, function(result) { 439 checkReceivedData(result); 440 test.listenOnce(serial.onReceive, function(result) { 441 checkReceivedData(result); 442 disconnect(); 443 }); 444 }); 445 }); 446 }, 447 448 // Test that a second send after the first fails is successful. This uses an 449 // IoHandler that returns system_error for only the first send. 450 function testSendPartialSuccessWithError() { 451 connect(function() { 452 sendData().then(test.callbackPass(function(sendInfo) { 453 test.assertEq(2, sendInfo.bytesSent); 454 test.assertEq('system_error', sendInfo.error); 455 return sendData(); 456 })).then(test.callbackPass(function(sendInfo) { 457 test.assertEq(4, sendInfo.bytesSent); 458 test.assertEq(undefined, sendInfo.error); 459 disconnect(); 460 })); 461 }); 462 }, 463 464 // Test that a timed-out send returns a timeout error and that changing the 465 // send timeout during a send does not affect its timeout. This test uses an 466 // IoHandle that never completes sends. 467 function testSendTimeout() { 468 connect(function() { 469 sendData().then(test.callbackPass(function(sendInfo) { 470 test.assertEq(0, sendInfo.bytesSent); 471 test.assertEq('timeout', sendInfo.error); 472 test.assertEq(5, timeoutManager.currentTime); 473 disconnect(); 474 })); 475 serial.update(connectionId, {sendTimeout: 10}, test.callbackPass( 476 timeoutManager.run.bind(timeoutManager, 1))); 477 }, {sendTimeout: 5}); 478 }, 479 480 // Test that a timed-out send returns a timeout error and that disabling the 481 // send timeout during a send does not affect its timeout. This test uses an 482 // IoHandle that never completes sends. 483 function testDisableSendTimeout() { 484 connect(function() { 485 sendData().then(test.callbackPass(function(sendInfo) { 486 test.assertEq(0, sendInfo.bytesSent); 487 test.assertEq('timeout', sendInfo.error); 488 test.assertEq(6, timeoutManager.currentTime); 489 disconnect(); 490 })); 491 serial.update(connectionId, {sendTimeout: 0}, test.callbackPass( 492 timeoutManager.run.bind(timeoutManager, 1))); 493 }, {sendTimeout: 6}); 494 }, 495 496 // Test that data received while the connection is paused is queued and 497 // dispatched once the connection is unpaused. 498 function testPausedReceive() { 499 // Wait until the receive hook is installed, then start the test. 500 addReceiveHook(function() { 501 // Unpause the connection after the connection has queued the received 502 // data to ensure the queued data is dispatched when the connection is 503 // unpaused. 504 serial.setPaused(connectionId, false, test.callbackPass()); 505 // Check that setPaused(false) is idempotent. 506 serial.setPaused(connectionId, false, test.callbackPass()); 507 }).then(function() { 508 connect(function() { 509 // Check that setPaused(true) is idempotent. 510 serial.setPaused(connectionId, true, test.callbackPass()); 511 serial.setPaused(connectionId, true, test.callbackPass()); 512 }); 513 }); 514 test.listenOnce(serial.onReceive, function(result) { 515 checkReceivedData(result); 516 disconnect(); 517 }); 518 }, 519 520 // Test that a receive error received while the connection is paused is queued 521 // and dispatched once the connection is unpaused. 522 function testPausedReceiveError() { 523 addReceiveErrorHook(function() { 524 // Unpause the connection after the connection has queued the receive 525 // error to ensure the queued error is dispatched when the connection is 526 // unpaused. 527 serial.setPaused(connectionId, false, test.callbackPass()); 528 }).then(test.callbackPass(function() { 529 connect(function() { 530 serial.setPaused(connectionId, true, test.callbackPass()); 531 }); 532 })); 533 534 test.listenOnce(serial.onReceiveError, function(result) { 535 serial.getInfo(connectionId, test.callbackPass(function(connectionInfo) { 536 disconnect(); 537 test.assertTrue(connectionInfo.paused); 538 })); 539 test.assertEq(connectionId, result.connectionId); 540 test.assertEq('device_lost', result.error); 541 }); 542 serial.onReceive.addListener(function() { 543 test.fail('unexpected onReceive event'); 544 }); 545 }, 546 547 // Test that receive timeouts trigger after the timeout time elapses and that 548 // changing the receive timeout does not affect a wait in progress. 549 function testReceiveTimeout() { 550 connect(function() { 551 test.listenOnce(serial.onReceiveError, function(result) { 552 test.assertEq(connectionId, result.connectionId); 553 test.assertEq('timeout', result.error); 554 test.assertEq(20, timeoutManager.currentTime); 555 serial.getInfo(connectionId, test.callbackPass( 556 function(connectionInfo) { 557 test.assertFalse(connectionInfo.paused); 558 disconnect(); 559 })); 560 }); 561 // Changing the timeout does not take effect until the current timeout 562 // expires or a receive completes. 563 serial.update(connectionId, {receiveTimeout: 10}, test.callbackPass( 564 timeoutManager.run.bind(timeoutManager, 1))); 565 }, {receiveTimeout: 20}); 566 }, 567 568 // Test that receive timeouts trigger after the timeout time elapses and that 569 // disabling the receive timeout does not affect a wait in progress. 570 function testDisableReceiveTimeout() { 571 connect(function() { 572 test.listenOnce(serial.onReceiveError, function(result) { 573 test.assertEq(connectionId, result.connectionId); 574 test.assertEq('timeout', result.error); 575 test.assertEq(30, timeoutManager.currentTime); 576 serial.getInfo(connectionId, test.callbackPass( 577 function(connectionInfo) { 578 disconnect(); 579 test.assertFalse(connectionInfo.paused); 580 })); 581 }); 582 // Disabling the timeout does not take effect until the current timeout 583 // expires or a receive completes. 584 serial.update(connectionId, {receiveTimeout: 0}, test.callbackPass( 585 timeoutManager.run.bind(timeoutManager, 1))); 586 }, {receiveTimeout: 30}); 587 }, 588 589 // Test that a receive error from the service is correctly dispatched. This 590 // test uses an IoHandler that only reports 'disconnected' receive errors. 591 function testReceiveErrorDisconnected() { 592 runReceiveErrorTest('disconnected'); 593 }, 594 595 // Test that a receive error from the service is correctly dispatched. This 596 // test uses an IoHandler that only reports 'device_lost' receive errors. 597 function testReceiveErrorDeviceLost() { 598 runReceiveErrorTest('device_lost'); 599 }, 600 601 // Test that a receive from error the service is correctly dispatched. This 602 // test uses an IoHandler that only reports 'system_error' receive errors. 603 function testReceiveErrorSystemError() { 604 runReceiveErrorTest('system_error'); 605 }, 606 607 // Test that a send error from the service is correctly returned as the send 608 // result. This test uses an IoHandler that only reports 'disconnected' send 609 // errors. 610 function testSendErrorDisconnected() { 611 runSendErrorTest('disconnected'); 612 }, 613 614 // Test that a send error from the service is correctly returned as the send 615 // result. This test uses an IoHandler that only reports 'system_error' send 616 // errors. 617 function testSendErrorSystemError() { 618 runSendErrorTest('system_error'); 619 }, 620 621 // Test that disconnect returns the correct error for a connection ID that 622 // does not exist. 623 function testDisconnectUnknownConnectionId() { 624 serial.disconnect(-1, test.callbackFail('Serial connection not found.')); 625 }, 626 627 // Test that getInfo returns the correct error for a connection ID that does 628 // not exist. 629 function testGetInfoUnknownConnectionId() { 630 serial.getInfo(-1, test.callbackFail('Serial connection not found.')); 631 }, 632 633 // Test that update returns the correct error for a connection ID that does 634 // not exist. 635 function testUpdateUnknownConnectionId() { 636 serial.update(-1, {}, test.callbackFail('Serial connection not found.')); 637 }, 638 639 // Test that setControlSignals returns the correct error for a connection ID 640 // that does not exist. 641 function testSetControlSignalsUnknownConnectionId() { 642 serial.setControlSignals(-1, {}, test.callbackFail( 643 'Serial connection not found.')); 644 }, 645 646 // Test that getControlSignals returns the correct error for a connection ID 647 // that does not exist. 648 function testGetControlSignalsUnknownConnectionId() { 649 serial.getControlSignals(-1, test.callbackFail( 650 'Serial connection not found.')); 651 }, 652 653 // Test that flush returns the correct error for a connection ID that does not 654 // exist. 655 function testFlushUnknownConnectionId() { 656 serial.flush(-1, test.callbackFail('Serial connection not found.')); 657 }, 658 659 // Test that setPaused returns the correct error for a connection ID that does 660 // not exist. 661 function testSetPausedUnknownConnectionId() { 662 serial.setPaused( 663 -1, true, test.callbackFail('Serial connection not found.')); 664 serial.setPaused( 665 -1, false, test.callbackFail('Serial connection not found.')); 666 }, 667 668 // Test that send returns the correct error for a connection ID that does not 669 // exist. 670 function testSendUnknownConnectionId() { 671 var buffer = new ArrayBuffer(1); 672 serial.send(-1, buffer, test.callbackFail('Serial connection not found.')); 673 }, 674 ], test.runTests, exports); 675