websocket_test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. // Copyright 2011 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.net.WebSocketTest');
  15. goog.setTestOnly('goog.net.WebSocketTest');
  16. goog.require('goog.debug.EntryPointMonitor');
  17. goog.require('goog.debug.ErrorHandler');
  18. goog.require('goog.debug.entryPointRegistry');
  19. goog.require('goog.events');
  20. goog.require('goog.functions');
  21. goog.require('goog.net.WebSocket');
  22. goog.require('goog.testing.MockClock');
  23. goog.require('goog.testing.PropertyReplacer');
  24. goog.require('goog.testing.jsunit');
  25. goog.require('goog.testing.recordFunction');
  26. var webSocket;
  27. var mockClock;
  28. var pr;
  29. var testUrl;
  30. var originalOnOpen = goog.net.WebSocket.prototype.onOpen_;
  31. var originalOnClose = goog.net.WebSocket.prototype.onClose_;
  32. var originalOnMessage = goog.net.WebSocket.prototype.onMessage_;
  33. var originalOnError = goog.net.WebSocket.prototype.onError_;
  34. function setUp() {
  35. pr = new goog.testing.PropertyReplacer();
  36. pr.set(goog.global, 'WebSocket', MockWebSocket);
  37. mockClock = new goog.testing.MockClock(true);
  38. testUrl = 'ws://127.0.0.1:4200';
  39. testProtocol = 'xmpp';
  40. }
  41. function tearDown() {
  42. pr.reset();
  43. goog.net.WebSocket.prototype.onOpen_ = originalOnOpen;
  44. goog.net.WebSocket.prototype.onClose_ = originalOnClose;
  45. goog.net.WebSocket.prototype.onMessage_ = originalOnMessage;
  46. goog.net.WebSocket.prototype.onError_ = originalOnError;
  47. goog.dispose(mockClock);
  48. goog.dispose(webSocket);
  49. }
  50. function testOpenInUnsupportingBrowserThrowsException() {
  51. // Null out WebSocket to simulate lack of support.
  52. if (goog.global.WebSocket) {
  53. goog.global.WebSocket = null;
  54. }
  55. webSocket = new goog.net.WebSocket();
  56. assertThrows('Open should fail if WebSocket is not defined.', function() {
  57. webSocket.open(testUrl);
  58. });
  59. }
  60. function testOpenTwiceThrowsException() {
  61. webSocket = new goog.net.WebSocket();
  62. webSocket.open(testUrl);
  63. simulateOpenEvent(webSocket.webSocket_);
  64. assertThrows('Attempting to open a second time should fail.', function() {
  65. webSocket.open(testUrl);
  66. });
  67. }
  68. function testSendWithoutOpeningThrowsException() {
  69. webSocket = new goog.net.WebSocket();
  70. assertThrows(
  71. 'Send should fail if the web socket was not first opened.',
  72. function() { webSocket.send('test message'); });
  73. }
  74. function testOpenWithProtocol() {
  75. webSocket = new goog.net.WebSocket();
  76. webSocket.open(testUrl, testProtocol);
  77. var ws = webSocket.webSocket_;
  78. simulateOpenEvent(ws);
  79. assertEquals(testUrl, ws.url);
  80. assertEquals(testProtocol, ws.protocol);
  81. }
  82. function testOpenAndClose() {
  83. webSocket = new goog.net.WebSocket();
  84. assertFalse(webSocket.isOpen());
  85. webSocket.open(testUrl);
  86. var ws = webSocket.webSocket_;
  87. simulateOpenEvent(ws);
  88. assertTrue(webSocket.isOpen());
  89. assertEquals(testUrl, ws.url);
  90. webSocket.close();
  91. simulateCloseEvent(ws);
  92. assertFalse(webSocket.isOpen());
  93. }
  94. function testReconnectionDisabled() {
  95. // Construct the web socket and disable reconnection.
  96. webSocket = new goog.net.WebSocket(false);
  97. // Record how many times open is called.
  98. pr.set(webSocket, 'open', goog.testing.recordFunction(webSocket.open));
  99. // Open the web socket.
  100. webSocket.open(testUrl);
  101. assertEquals(0, webSocket.reconnectAttempt_);
  102. assertEquals(1, webSocket.open.getCallCount());
  103. assertFalse(webSocket.isOpen());
  104. // Simulate failure.
  105. var ws = webSocket.webSocket_;
  106. simulateCloseEvent(ws);
  107. assertFalse(webSocket.isOpen());
  108. assertEquals(0, webSocket.reconnectAttempt_);
  109. assertEquals(1, webSocket.open.getCallCount());
  110. // Make sure a reconnection doesn't happen.
  111. mockClock.tick(100000);
  112. assertEquals(0, webSocket.reconnectAttempt_);
  113. assertEquals(1, webSocket.open.getCallCount());
  114. }
  115. function testReconnectionWithFailureOnFirstOpen() {
  116. // Construct the web socket with a linear back-off.
  117. webSocket = new goog.net.WebSocket(true, linearBackOff);
  118. // Record how many times open is called.
  119. pr.set(webSocket, 'open', goog.testing.recordFunction(webSocket.open));
  120. // Open the web socket.
  121. webSocket.open(testUrl, testProtocol);
  122. assertEquals(0, webSocket.reconnectAttempt_);
  123. assertEquals(1, webSocket.open.getCallCount());
  124. assertFalse(webSocket.isOpen());
  125. // Simulate failure.
  126. var ws = webSocket.webSocket_;
  127. simulateCloseEvent(ws);
  128. assertFalse(webSocket.isOpen());
  129. assertEquals(1, webSocket.reconnectAttempt_);
  130. assertEquals(1, webSocket.open.getCallCount());
  131. // Make sure the reconnect doesn't happen before it should.
  132. mockClock.tick(linearBackOff(0) - 1);
  133. assertEquals(1, webSocket.open.getCallCount());
  134. mockClock.tick(1);
  135. assertEquals(2, webSocket.open.getCallCount());
  136. // Simulate another failure.
  137. simulateCloseEvent(ws);
  138. assertFalse(webSocket.isOpen());
  139. assertEquals(2, webSocket.reconnectAttempt_);
  140. assertEquals(2, webSocket.open.getCallCount());
  141. // Make sure the reconnect doesn't happen before it should.
  142. mockClock.tick(linearBackOff(1) - 1);
  143. assertEquals(2, webSocket.open.getCallCount());
  144. mockClock.tick(1);
  145. assertEquals(3, webSocket.open.getCallCount());
  146. // Simulate connection success.
  147. simulateOpenEvent(ws);
  148. assertEquals(0, webSocket.reconnectAttempt_);
  149. assertEquals(3, webSocket.open.getCallCount());
  150. // Make sure the reconnection has the same url and protocol.
  151. assertEquals(testUrl, ws.url);
  152. assertEquals(testProtocol, ws.protocol);
  153. // Ensure no further calls to open are made.
  154. mockClock.tick(linearBackOff(10));
  155. assertEquals(3, webSocket.open.getCallCount());
  156. }
  157. function testReconnectionWithFailureAfterOpen() {
  158. // Construct the web socket with a linear back-off.
  159. webSocket = new goog.net.WebSocket(true, fibonacciBackOff);
  160. // Record how many times open is called.
  161. pr.set(webSocket, 'open', goog.testing.recordFunction(webSocket.open));
  162. // Open the web socket.
  163. webSocket.open(testUrl);
  164. assertEquals(0, webSocket.reconnectAttempt_);
  165. assertEquals(1, webSocket.open.getCallCount());
  166. assertFalse(webSocket.isOpen());
  167. // Simulate connection success.
  168. var ws = webSocket.webSocket_;
  169. simulateOpenEvent(ws);
  170. assertEquals(0, webSocket.reconnectAttempt_);
  171. assertEquals(1, webSocket.open.getCallCount());
  172. // Let some time pass, then fail the connection.
  173. mockClock.tick(100000);
  174. simulateCloseEvent(ws);
  175. assertFalse(webSocket.isOpen());
  176. assertEquals(1, webSocket.reconnectAttempt_);
  177. assertEquals(1, webSocket.open.getCallCount());
  178. // Make sure the reconnect doesn't happen before it should.
  179. mockClock.tick(fibonacciBackOff(0) - 1);
  180. assertEquals(1, webSocket.open.getCallCount());
  181. mockClock.tick(1);
  182. assertEquals(2, webSocket.open.getCallCount());
  183. // Simulate connection success.
  184. ws = webSocket.webSocket_;
  185. simulateOpenEvent(ws);
  186. assertEquals(0, webSocket.reconnectAttempt_);
  187. assertEquals(2, webSocket.open.getCallCount());
  188. // Ensure no further calls to open are made.
  189. mockClock.tick(fibonacciBackOff(10));
  190. assertEquals(2, webSocket.open.getCallCount());
  191. }
  192. function testExponentialBackOff() {
  193. assertEquals(1000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(0));
  194. assertEquals(2000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(1));
  195. assertEquals(4000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(2));
  196. assertEquals(60000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(6));
  197. assertEquals(60000, goog.net.WebSocket.EXPONENTIAL_BACKOFF_(7));
  198. }
  199. function testEntryPointRegistry() {
  200. var monitor = new goog.debug.EntryPointMonitor();
  201. var replacement = function() {};
  202. monitor.wrap =
  203. goog.testing.recordFunction(goog.functions.constant(replacement));
  204. goog.debug.entryPointRegistry.monitorAll(monitor);
  205. assertTrue(monitor.wrap.getCallCount() >= 1);
  206. assertEquals(replacement, goog.net.WebSocket.prototype.onOpen_);
  207. assertEquals(replacement, goog.net.WebSocket.prototype.onClose_);
  208. assertEquals(replacement, goog.net.WebSocket.prototype.onMessage_);
  209. assertEquals(replacement, goog.net.WebSocket.prototype.onError_);
  210. }
  211. function testErrorHandlerCalled() {
  212. var errorHandlerCalled = false;
  213. var errorHandler =
  214. new goog.debug.ErrorHandler(function() { errorHandlerCalled = true; });
  215. goog.net.WebSocket.protectEntryPoints(errorHandler);
  216. webSocket = new goog.net.WebSocket();
  217. goog.events.listenOnce(
  218. webSocket, goog.net.WebSocket.EventType.OPENED,
  219. function() { throw Error(); });
  220. webSocket.open(testUrl);
  221. var ws = webSocket.webSocket_;
  222. assertThrows(function() { simulateOpenEvent(ws); });
  223. assertTrue(
  224. 'Error handler callback should be called when registered as ' +
  225. 'protecting the entry points.',
  226. errorHandlerCalled);
  227. }
  228. /**
  229. * Simulates the browser firing the open event for the given web socket.
  230. * @param {MockWebSocket} ws The mock web socket.
  231. */
  232. function simulateOpenEvent(ws) {
  233. ws.readyState = goog.net.WebSocket.ReadyState_.OPEN;
  234. ws.onopen();
  235. }
  236. /**
  237. * Simulates the browser firing the close event for the given web socket.
  238. * @param {MockWebSocket} ws The mock web socket.
  239. */
  240. function simulateCloseEvent(ws) {
  241. ws.readyState = goog.net.WebSocket.ReadyState_.CLOSED;
  242. ws.onclose({data: 'mock close event'});
  243. }
  244. /**
  245. * Strategy for reconnection that backs off linearly with a 1 second offset.
  246. * @param {number} attempt The number of reconnects since the last connection.
  247. * @return {number} The amount of time to the next reconnect, in milliseconds.
  248. */
  249. function linearBackOff(attempt) {
  250. return (attempt * 1000) + 1000;
  251. }
  252. /**
  253. * Strategy for reconnection that backs off with the fibonacci pattern. It is
  254. * offset by 5 seconds so the first attempt will happen after 5 seconds.
  255. * @param {number} attempt The number of reconnects since the last connection.
  256. * @return {number} The amount of time to the next reconnect, in milliseconds.
  257. */
  258. function fibonacciBackOff(attempt) {
  259. return (fibonacci(attempt) * 1000) + 5000;
  260. }
  261. /**
  262. * Computes the desired fibonacci number.
  263. * @param {number} n The nth desired fibonacci number.
  264. * @return {number} The nth fibonacci number.
  265. */
  266. function fibonacci(n) {
  267. if (n == 0) {
  268. return 0;
  269. } else if (n == 1) {
  270. return 1;
  271. } else {
  272. return fibonacci(n - 2) + fibonacci(n - 1);
  273. }
  274. }
  275. /**
  276. * Mock WebSocket constructor.
  277. * @param {string} url The url to the web socket server.
  278. * @param {string} protocol The protocol to use.
  279. * @constructor
  280. */
  281. MockWebSocket = function(url, protocol) {
  282. this.url = url;
  283. this.protocol = protocol;
  284. this.readyState = goog.net.WebSocket.ReadyState_.CONNECTING;
  285. };
  286. /**
  287. * Mocks out the close method of the WebSocket.
  288. */
  289. MockWebSocket.prototype.close = function() {
  290. this.readyState = goog.net.WebSocket.ReadyState_.CLOSING;
  291. };
  292. /**
  293. * Mocks out the send method of the WebSocket.
  294. */
  295. MockWebSocket.prototype.send = function() {
  296. // Nothing to do here.
  297. };