| // Copyright 2011 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide('goog.net.WebSocketTest'); |
| goog.setTestOnly('goog.net.WebSocketTest'); |
| |
| goog.require('goog.debug.EntryPointMonitor'); |
| goog.require('goog.debug.ErrorHandler'); |
| goog.require('goog.debug.entryPointRegistry'); |
| goog.require('goog.events'); |
| goog.require('goog.functions'); |
| goog.require('goog.net.WebSocket'); |
| goog.require('goog.testing.MockClock'); |
| goog.require('goog.testing.PropertyReplacer'); |
| goog.require('goog.testing.jsunit'); |
| goog.require('goog.testing.recordFunction'); |
| |
| var webSocket; |
| var mockClock; |
| var pr; |
| var testUrl; |
| |
| var originalOnOpen = goog.net.WebSocket.prototype.onOpen_; |
| var originalOnClose = goog.net.WebSocket.prototype.onClose_; |
| var originalOnMessage = goog.net.WebSocket.prototype.onMessage_; |
| var originalOnError = goog.net.WebSocket.prototype.onError_; |
| |
| function setUp() { |
| pr = new goog.testing.PropertyReplacer(); |
| pr.set(goog.global, 'WebSocket', MockWebSocket); |
| mockClock = new goog.testing.MockClock(true); |
| testUrl = 'ws://127.0.0.1:4200'; |
| testProtocol = 'xmpp'; |
| } |
| |
| function tearDown() { |
| pr.reset(); |
| goog.net.WebSocket.prototype.onOpen_ = originalOnOpen; |
| goog.net.WebSocket.prototype.onClose_ = originalOnClose; |
| goog.net.WebSocket.prototype.onMessage_ = originalOnMessage; |
| goog.net.WebSocket.prototype.onError_ = originalOnError; |
| goog.dispose(mockClock); |
| goog.dispose(webSocket); |
| } |
| |
| function testOpenInUnsupportingBrowserThrowsException() { |
| // Null out WebSocket to simulate lack of support. |
| if (goog.global.WebSocket) { |
| goog.global.WebSocket = null; |
| } |
| |
| webSocket = new goog.net.WebSocket(); |
| assertThrows('Open should fail if WebSocket is not defined.', |
| function() { |
| webSocket.open(testUrl); |
| }); |
| } |
| |
| function testOpenTwiceThrowsException() { |
| webSocket = new goog.net.WebSocket(); |
| webSocket.open(testUrl); |
| simulateOpenEvent(webSocket.webSocket_); |
| |
| assertThrows('Attempting to open a second time should fail.', |
| function() { |
| webSocket.open(testUrl); |
| }); |
| } |
| |
| function testSendWithoutOpeningThrowsException() { |
| webSocket = new goog.net.WebSocket(); |
| |
| assertThrows('Send should fail if the web socket was not first opened.', |
| function() { |
| webSocket.send('test message'); |
| }); |
| } |
| |
| function testOpenWithProtocol() { |
| webSocket = new goog.net.WebSocket(); |
| webSocket.open(testUrl, testProtocol); |
| var ws = webSocket.webSocket_; |
| simulateOpenEvent(ws); |
| assertEquals(testUrl, ws.url); |
| assertEquals(testProtocol, ws.protocol); |
| } |
| |
| function testOpenAndClose() { |
| webSocket = new goog.net.WebSocket(); |
| assertFalse(webSocket.isOpen()); |
| webSocket.open(testUrl); |
| var ws = webSocket.webSocket_; |
| simulateOpenEvent(ws); |
| assertTrue(webSocket.isOpen()); |
| assertEquals(testUrl, ws.url); |
| webSocket.close(); |
| simulateCloseEvent(ws); |
| assertFalse(webSocket.isOpen()); |
| } |
| |
| function testReconnectionDisabled() { |
| // Construct the web socket and disable reconnection. |
| webSocket = new goog.net.WebSocket(false); |
| |
| // Record how many times open is called. |
| pr.set(webSocket, 'open', goog.testing.recordFunction(webSocket.open)); |
| |
| // Open the web socket. |
| webSocket.open(testUrl); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| assertFalse(webSocket.isOpen()); |
| |
| // Simulate failure. |
| var ws = webSocket.webSocket_; |
| simulateCloseEvent(ws); |
| assertFalse(webSocket.isOpen()); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| |
| // Make sure a reconnection doesn't happen. |
| mockClock.tick(100000); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| } |
| |
| function testReconnectionWithFailureOnFirstOpen() { |
| // Construct the web socket with a linear back-off. |
| webSocket = new goog.net.WebSocket(true, linearBackOff); |
| |
| // Record how many times open is called. |
| pr.set(webSocket, 'open', goog.testing.recordFunction(webSocket.open)); |
| |
| // Open the web socket. |
| webSocket.open(testUrl, testProtocol); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| assertFalse(webSocket.isOpen()); |
| |
| // Simulate failure. |
| var ws = webSocket.webSocket_; |
| simulateCloseEvent(ws); |
| assertFalse(webSocket.isOpen()); |
| assertEquals(1, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| |
| // Make sure the reconnect doesn't happen before it should. |
| mockClock.tick(linearBackOff(0) - 1); |
| assertEquals(1, webSocket.open.getCallCount()); |
| mockClock.tick(1); |
| assertEquals(2, webSocket.open.getCallCount()); |
| |
| // Simulate another failure. |
| simulateCloseEvent(ws); |
| assertFalse(webSocket.isOpen()); |
| assertEquals(2, webSocket.reconnectAttempt_); |
| assertEquals(2, webSocket.open.getCallCount()); |
| |
| // Make sure the reconnect doesn't happen before it should. |
| mockClock.tick(linearBackOff(1) - 1); |
| assertEquals(2, webSocket.open.getCallCount()); |
| mockClock.tick(1); |
| assertEquals(3, webSocket.open.getCallCount()); |
| |
| // Simulate connection success. |
| simulateOpenEvent(ws); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(3, webSocket.open.getCallCount()); |
| |
| // Make sure the reconnection has the same url and protocol. |
| assertEquals(testUrl, ws.url); |
| assertEquals(testProtocol, ws.protocol); |
| |
| // Ensure no further calls to open are made. |
| mockClock.tick(linearBackOff(10)); |
| assertEquals(3, webSocket.open.getCallCount()); |
| } |
| |
| function testReconnectionWithFailureAfterOpen() { |
| // Construct the web socket with a linear back-off. |
| webSocket = new goog.net.WebSocket(true, fibonacciBackOff); |
| |
| // Record how many times open is called. |
| pr.set(webSocket, 'open', goog.testing.recordFunction(webSocket.open)); |
| |
| // Open the web socket. |
| webSocket.open(testUrl); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| assertFalse(webSocket.isOpen()); |
| |
| // Simulate connection success. |
| var ws = webSocket.webSocket_; |
| simulateOpenEvent(ws); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| |
| // Let some time pass, then fail the connection. |
| mockClock.tick(100000); |
| simulateCloseEvent(ws); |
| assertFalse(webSocket.isOpen()); |
| assertEquals(1, webSocket.reconnectAttempt_); |
| assertEquals(1, webSocket.open.getCallCount()); |
| |
| // Make sure the reconnect doesn't happen before it should. |
| mockClock.tick(fibonacciBackOff(0) - 1); |
| assertEquals(1, webSocket.open.getCallCount()); |
| mockClock.tick(1); |
| assertEquals(2, webSocket.open.getCallCount()); |
| |
| // Simulate connection success. |
| ws = webSocket.webSocket_; |
| simulateOpenEvent(ws); |
| assertEquals(0, webSocket.reconnectAttempt_); |
| assertEquals(2, webSocket.open.getCallCount()); |
| |
| // Ensure no further calls to open are made. |
| mockClock.tick(fibonacciBackOff(10)); |
| assertEquals(2, webSocket.open.getCallCount()); |
| } |
| |
| function testExponentialBackOff() { |
| assertEquals(1000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(0)); |
| assertEquals(2000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(1)); |
| assertEquals(4000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(2)); |
| assertEquals(60000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(6)); |
| assertEquals(60000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(7)); |
| } |
| |
| function testEntryPointRegistry() { |
| var monitor = new goog.debug.EntryPointMonitor(); |
| var replacement = function() {}; |
| monitor.wrap = goog.testing.recordFunction( |
| goog.functions.constant(replacement)); |
| |
| goog.debug.entryPointRegistry.monitorAll(monitor); |
| assertTrue(monitor.wrap.getCallCount() >= 1); |
| assertEquals(replacement, goog.net.WebSocket.prototype.onOpen_); |
| assertEquals(replacement, goog.net.WebSocket.prototype.onClose_); |
| assertEquals(replacement, goog.net.WebSocket.prototype.onMessage_); |
| assertEquals(replacement, goog.net.WebSocket.prototype.onError_); |
| } |
| |
| function testErrorHandlerCalled() { |
| var errorHandlerCalled = false; |
| var errorHandler = new goog.debug.ErrorHandler(function() { |
| errorHandlerCalled = true; |
| }); |
| goog.net.WebSocket.protectEntryPoints(errorHandler); |
| |
| webSocket = new goog.net.WebSocket(); |
| goog.events.listenOnce(webSocket, goog.net.WebSocket.EventType.OPENED, |
| function() { |
| throw Error(); |
| }); |
| |
| webSocket.open(testUrl); |
| var ws = webSocket.webSocket_; |
| assertThrows(function() { |
| simulateOpenEvent(ws); |
| }); |
| |
| assertTrue('Error handler callback should be called when registered as ' + |
| 'protecting the entry points.', errorHandlerCalled); |
| } |
| |
| |
| /** |
| * Simulates the browser firing the open event for the given web socket. |
| * @param {MockWebSocket} ws The mock web socket. |
| */ |
| function simulateOpenEvent(ws) { |
| ws.readyState = goog.net.WebSocket.ReadyState_.OPEN; |
| ws.onopen(); |
| } |
| |
| |
| /** |
| * Simulates the browser firing the close event for the given web socket. |
| * @param {MockWebSocket} ws The mock web socket. |
| */ |
| function simulateCloseEvent(ws) { |
| ws.readyState = goog.net.WebSocket.ReadyState_.CLOSED; |
| ws.onclose({data: 'mock close event'}); |
| } |
| |
| |
| /** |
| * Strategy for reconnection that backs off linearly with a 1 second offset. |
| * @param {number} attempt The number of reconnects since the last connection. |
| * @return {number} The amount of time to the next reconnect, in milliseconds. |
| */ |
| function linearBackOff(attempt) { |
| return (attempt * 1000) + 1000; |
| } |
| |
| |
| /** |
| * Strategy for reconnection that backs off with the fibonacci pattern. It is |
| * offset by 5 seconds so the first attempt will happen after 5 seconds. |
| * @param {number} attempt The number of reconnects since the last connection. |
| * @return {number} The amount of time to the next reconnect, in milliseconds. |
| */ |
| function fibonacciBackOff(attempt) { |
| return (fibonacci(attempt) * 1000) + 5000; |
| } |
| |
| |
| /** |
| * Computes the desired fibonacci number. |
| * @param {number} n The nth desired fibonacci number. |
| * @return {number} The nth fibonacci number. |
| */ |
| function fibonacci(n) { |
| if (n == 0) { |
| return 0; |
| } else if (n == 1) { |
| return 1; |
| } else { |
| return fibonacci(n - 2) + fibonacci(n - 1); |
| } |
| } |
| |
| |
| |
| /** |
| * Mock WebSocket constructor. |
| * @param {string} url The url to the web socket server. |
| * @param {string} protocol The protocol to use. |
| * @constructor |
| */ |
| MockWebSocket = function(url, protocol) { |
| this.url = url; |
| this.protocol = protocol; |
| this.readyState = goog.net.WebSocket.ReadyState_.CONNECTING; |
| }; |
| |
| |
| /** |
| * Mocks out the close method of the WebSocket. |
| */ |
| MockWebSocket.prototype.close = function() { |
| this.readyState = goog.net.WebSocket.ReadyState_.CLOSING; |
| }; |
| |
| |
| /** |
| * Mocks out the send method of the WebSocket. |
| */ |
| MockWebSocket.prototype.send = function() { |
| // Nothing to do here. |
| }; |