| // Copyright 2009 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.BrowserChannelTest'); |
| goog.setTestOnly('goog.net.BrowserChannelTest'); |
| |
| goog.require('goog.Timer'); |
| goog.require('goog.array'); |
| goog.require('goog.dom'); |
| goog.require('goog.functions'); |
| goog.require('goog.json'); |
| goog.require('goog.net.BrowserChannel'); |
| goog.require('goog.net.ChannelDebug'); |
| goog.require('goog.net.ChannelRequest'); |
| goog.require('goog.net.tmpnetwork'); |
| goog.require('goog.structs.Map'); |
| goog.require('goog.testing.MockClock'); |
| goog.require('goog.testing.PropertyReplacer'); |
| goog.require('goog.testing.asserts'); |
| goog.require('goog.testing.jsunit'); |
| goog.require('goog.testing.recordFunction'); |
| |
| |
| /** |
| * Delay between a network failure and the next network request. |
| */ |
| var RETRY_TIME = 1000; |
| |
| |
| /** |
| * A really long time - used to make sure no more timeouts will fire. |
| */ |
| var ALL_DAY_MS = 1000 * 60 * 60 * 24; |
| |
| var stubs = new goog.testing.PropertyReplacer(); |
| |
| var browserChannel; |
| var deliveredMaps; |
| var handler; |
| var mockClock; |
| var gotError; |
| var numStatEvents; |
| var lastStatEvent; |
| var numTimingEvents; |
| var lastPostSize; |
| var lastPostRtt; |
| var lastPostRetryCount; |
| |
| // Set to true to see the channel debug output in the browser window. |
| var debug = false; |
| // Debug message to print out when debug is true. |
| var debugMessage = ''; |
| |
| function debugToWindow(message) { |
| if (debug) { |
| debugMessage += message + '<br>'; |
| goog.dom.getElement('debug').innerHTML = debugMessage; |
| } |
| } |
| |
| |
| /** |
| * Stubs goog.net.tmpnetwork to always time out. It maintains the |
| * contract given by goog.net.tmpnetwork.testGoogleCom, but always |
| * times out (calling callback(false)). |
| * |
| * stubTmpnetwork should be called in tests that require it before |
| * a call to testGoogleCom happens. It is reset at tearDown. |
| */ |
| function stubTmpnetwork() { |
| stubs.set(goog.net.tmpnetwork, 'testLoadImage', |
| function(url, timeout, callback) { |
| goog.Timer.callOnce(goog.partial(callback, false), timeout); |
| }); |
| } |
| |
| |
| |
| /** |
| * Mock ChannelRequest. |
| * @constructor |
| */ |
| var MockChannelRequest = function(channel, channelDebug, opt_sessionId, |
| opt_requestId, opt_retryId) { |
| this.channel_ = channel; |
| this.channelDebug_ = channelDebug; |
| this.sessionId_ = opt_sessionId; |
| this.requestId_ = opt_requestId; |
| this.successful_ = true; |
| this.lastError_ = null; |
| this.lastStatusCode_ = 200; |
| |
| // For debugging, keep track of whether this is a back or forward channel. |
| this.isBack = !!(opt_requestId == 'rpc'); |
| this.isForward = !this.isBack; |
| }; |
| |
| MockChannelRequest.prototype.postData_ = null; |
| |
| MockChannelRequest.prototype.requestStartTime_ = null; |
| |
| MockChannelRequest.prototype.setExtraHeaders = function(extraHeaders) {}; |
| |
| MockChannelRequest.prototype.setTimeout = function(timeout) {}; |
| |
| MockChannelRequest.prototype.setReadyStateChangeThrottle = |
| function(throttle) {}; |
| |
| MockChannelRequest.prototype.xmlHttpPost = function(uri, postData, |
| decodeChunks) { |
| this.channelDebug_.debug('---> POST: ' + uri + ', ' + postData + ', ' + |
| decodeChunks); |
| this.postData_ = postData; |
| this.requestStartTime_ = goog.now(); |
| }; |
| |
| MockChannelRequest.prototype.xmlHttpGet = function(uri, decodeChunks, |
| opt_noClose) { |
| this.channelDebug_.debug('<--- GET: ' + uri + ', ' + decodeChunks + ', ' + |
| opt_noClose); |
| this.requestStartTime_ = goog.now(); |
| }; |
| |
| MockChannelRequest.prototype.tridentGet = function(uri, usingSecondaryDomain) { |
| this.channelDebug_.debug('<---GET (T): ' + uri); |
| this.requestStartTime_ = goog.now(); |
| }; |
| |
| MockChannelRequest.prototype.sendUsingImgTag = function(uri) { |
| this.requestStartTime_ = goog.now(); |
| }; |
| |
| MockChannelRequest.prototype.cancel = function() { |
| this.successful_ = false; |
| }; |
| |
| MockChannelRequest.prototype.getSuccess = function() { |
| return this.successful_; |
| }; |
| |
| MockChannelRequest.prototype.getLastError = function() { |
| return this.lastError_; |
| }; |
| |
| MockChannelRequest.prototype.getLastStatusCode = function() { |
| return this.lastStatusCode_; |
| }; |
| |
| MockChannelRequest.prototype.getSessionId = function() { |
| return this.sessionId_; |
| }; |
| |
| MockChannelRequest.prototype.getRequestId = function() { |
| return this.requestId_; |
| }; |
| |
| MockChannelRequest.prototype.getPostData = function() { |
| return this.postData_; |
| }; |
| |
| MockChannelRequest.prototype.getRequestStartTime = function() { |
| return this.requestStartTime_; |
| }; |
| |
| |
| function setUpPage() { |
| // Use our MockChannelRequests instead of the real ones. |
| goog.net.BrowserChannel.createChannelRequest = function( |
| channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId) { |
| return new MockChannelRequest( |
| channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId); |
| }; |
| |
| // Mock out the stat notification code. |
| goog.net.BrowserChannel.notifyStatEvent = function(stat) { |
| numStatEvents++; |
| lastStatEvent = stat; |
| }; |
| |
| goog.net.BrowserChannel.notifyTimingEvent = function(size, rtt, retries) { |
| numTimingEvents++; |
| lastPostSize = size; |
| lastPostRtt = rtt; |
| lastPostRetryCount = retries; |
| }; |
| } |
| |
| |
| function setUp() { |
| numTimingEvents = 0; |
| lastPostSize = null; |
| lastPostRtt = null; |
| lastPostRetryCount = null; |
| |
| mockClock = new goog.testing.MockClock(true); |
| browserChannel = new goog.net.BrowserChannel('1'); |
| gotError = false; |
| |
| handler = new goog.net.BrowserChannel.Handler(); |
| handler.channelOpened = function() {}; |
| handler.channelError = function(channel, error) { |
| gotError = true; |
| }; |
| handler.channelSuccess = function(channel, maps) { |
| deliveredMaps = goog.array.clone(maps); |
| }; |
| handler.channelClosed = function( |
| channel, opt_pendingMaps, opt_undeliveredMaps) { |
| // Mock out the handler, and let it set a formatted user readable string |
| // of the undelivered maps which we can use when verifying our assertions. |
| if (opt_pendingMaps) { |
| this.pendingMapsString = formatArrayOfMaps(opt_pendingMaps); |
| } |
| if (opt_undeliveredMaps) { |
| this.undeliveredMapsString = formatArrayOfMaps(opt_undeliveredMaps); |
| } |
| }; |
| handler.channelHandleMultipleArrays = function() {}; |
| handler.channelHandleArray = function() {}; |
| |
| browserChannel.setHandler(handler); |
| |
| // Provide a predictable retry time for testing. |
| browserChannel.getRetryTime_ = function(retryCount) { |
| return RETRY_TIME; |
| }; |
| |
| var channelDebug = new goog.net.ChannelDebug(); |
| channelDebug.debug = function(message) { |
| debugToWindow(message); |
| }; |
| browserChannel.setChannelDebug(channelDebug); |
| |
| numStatEvents = 0; |
| lastStatEvent = null; |
| } |
| |
| |
| function tearDown() { |
| mockClock.dispose(); |
| stubs.reset(); |
| debugToWindow('<hr>'); |
| } |
| |
| |
| /** |
| * Helper function to return a formatted string representing an array of maps. |
| */ |
| function formatArrayOfMaps(arrayOfMaps) { |
| var result = []; |
| for (var i = 0; i < arrayOfMaps.length; i++) { |
| var map = arrayOfMaps[i]; |
| var keys = map.map.getKeys(); |
| for (var j = 0; j < keys.length; j++) { |
| var tmp = keys[j] + ':' + map.map.get(keys[j]) + (map.context ? |
| ':' + map.context : ''); |
| result.push(tmp); |
| } |
| } |
| return result.join(', '); |
| } |
| |
| |
| function testFormatArrayOfMaps() { |
| // This function is used in a non-trivial test, so let's verify that it works. |
| var map1 = new goog.structs.Map(); |
| map1.set('k1', 'v1'); |
| map1.set('k2', 'v2'); |
| var map2 = new goog.structs.Map(); |
| map2.set('k3', 'v3'); |
| var map3 = new goog.structs.Map(); |
| map3.set('k4', 'v4'); |
| map3.set('k5', 'v5'); |
| map3.set('k6', 'v6'); |
| |
| // One map. |
| var a = []; |
| a.push(new goog.net.BrowserChannel.QueuedMap(0, map1)); |
| assertEquals('k1:v1, k2:v2', |
| formatArrayOfMaps(a)); |
| |
| // Many maps. |
| var b = []; |
| b.push(new goog.net.BrowserChannel.QueuedMap(0, map1)); |
| b.push(new goog.net.BrowserChannel.QueuedMap(0, map2)); |
| b.push(new goog.net.BrowserChannel.QueuedMap(0, map3)); |
| assertEquals('k1:v1, k2:v2, k3:v3, k4:v4, k5:v5, k6:v6', |
| formatArrayOfMaps(b)); |
| |
| // One map with a context. |
| var c = []; |
| c.push(new goog.net.BrowserChannel.QueuedMap(0, map1, 'c1')); |
| assertEquals('k1:v1:c1, k2:v2:c1', |
| formatArrayOfMaps(c)); |
| } |
| |
| |
| function connectForwardChannel( |
| opt_serverVersion, opt_hostPrefix, opt_uriPrefix) { |
| var uriPrefix = opt_uriPrefix || ''; |
| browserChannel.connect(uriPrefix + '/test', uriPrefix + '/bind', null); |
| mockClock.tick(0); |
| completeTestConnection(); |
| completeForwardChannel(opt_serverVersion, opt_hostPrefix); |
| } |
| |
| |
| function connect(opt_serverVersion, opt_hostPrefix, opt_uriPrefix) { |
| connectForwardChannel(opt_serverVersion, opt_hostPrefix, opt_uriPrefix); |
| completeBackChannel(); |
| } |
| |
| |
| function disconnect() { |
| browserChannel.disconnect(); |
| mockClock.tick(0); |
| } |
| |
| |
| function completeTestConnection() { |
| completeForwardTestConnection(); |
| completeBackTestConnection(); |
| assertEquals(goog.net.BrowserChannel.State.OPENING, |
| browserChannel.getState()); |
| } |
| |
| |
| function completeForwardTestConnection() { |
| browserChannel.connectionTest_.onRequestData( |
| browserChannel.connectionTest_, |
| '["b"]'); |
| browserChannel.connectionTest_.onRequestComplete( |
| browserChannel.connectionTest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function completeBackTestConnection() { |
| browserChannel.connectionTest_.onRequestData( |
| browserChannel.connectionTest_, |
| '11111'); |
| mockClock.tick(0); |
| } |
| |
| |
| function completeForwardChannel(opt_serverVersion, opt_hostPrefix) { |
| var responseData = '[[0,["c","1234567890ABCDEF",' + |
| (opt_hostPrefix ? '"' + opt_hostPrefix + '"' : 'null') + |
| (opt_serverVersion ? ',' + opt_serverVersion : '') + |
| ']]]'; |
| browserChannel.onRequestData( |
| browserChannel.forwardChannelRequest_, |
| responseData); |
| browserChannel.onRequestComplete( |
| browserChannel.forwardChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function completeBackChannel() { |
| browserChannel.onRequestData( |
| browserChannel.backChannelRequest_, |
| '[[1,["foo"]]]'); |
| browserChannel.onRequestComplete( |
| browserChannel.backChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function responseVersion7() { |
| browserChannel.onRequestData( |
| browserChannel.forwardChannelRequest_, |
| goog.net.BrowserChannel.MAGIC_RESPONSE_COOKIE); |
| browserChannel.onRequestComplete( |
| browserChannel.forwardChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| function responseNoBackchannel(lastArrayIdSentFromServer, outstandingDataSize) { |
| responseData = goog.json.serialize( |
| [0, lastArrayIdSentFromServer, outstandingDataSize]); |
| browserChannel.onRequestData( |
| browserChannel.forwardChannelRequest_, |
| responseData); |
| browserChannel.onRequestComplete( |
| browserChannel.forwardChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| function response(lastArrayIdSentFromServer, outstandingDataSize) { |
| responseData = goog.json.serialize( |
| [1, lastArrayIdSentFromServer, outstandingDataSize]); |
| browserChannel.onRequestData( |
| browserChannel.forwardChannelRequest_, |
| responseData |
| ); |
| browserChannel.onRequestComplete( |
| browserChannel.forwardChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function receive(data) { |
| browserChannel.onRequestData( |
| browserChannel.backChannelRequest_, |
| '[[1,' + data + ']]'); |
| browserChannel.onRequestComplete( |
| browserChannel.backChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function responseTimeout() { |
| browserChannel.forwardChannelRequest_lastError_ = |
| goog.net.ChannelRequest.Error.TIMEOUT; |
| browserChannel.forwardChannelRequest_.successful_ = false; |
| browserChannel.onRequestComplete( |
| browserChannel.forwardChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function responseRequestFailed(opt_statusCode) { |
| browserChannel.forwardChannelRequest_.lastError_ = |
| goog.net.ChannelRequest.Error.STATUS; |
| browserChannel.forwardChannelRequest_.lastStatusCode_ = |
| opt_statusCode || 503; |
| browserChannel.forwardChannelRequest_.successful_ = false; |
| browserChannel.onRequestComplete( |
| browserChannel.forwardChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function responseUnknownSessionId() { |
| browserChannel.forwardChannelRequest_.lastError_ = |
| goog.net.ChannelRequest.Error.UNKNOWN_SESSION_ID; |
| browserChannel.forwardChannelRequest_.successful_ = false; |
| browserChannel.onRequestComplete( |
| browserChannel.forwardChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function responseActiveXBlocked() { |
| browserChannel.backChannelRequest_.lastError_ = |
| goog.net.ChannelRequest.Error.ACTIVE_X_BLOCKED; |
| browserChannel.backChannelRequest_.successful_ = false; |
| browserChannel.onRequestComplete( |
| browserChannel.backChannelRequest_); |
| mockClock.tick(0); |
| } |
| |
| |
| function sendMap(key, value, opt_context) { |
| var map = new goog.structs.Map(); |
| map.set(key, value); |
| browserChannel.sendMap(map, opt_context); |
| mockClock.tick(0); |
| } |
| |
| |
| function hasForwardChannel() { |
| return !!browserChannel.forwardChannelRequest_; |
| } |
| |
| |
| function hasBackChannel() { |
| return !!browserChannel.backChannelRequest_; |
| } |
| |
| |
| function hasDeadBackChannelTimer() { |
| return goog.isDefAndNotNull(browserChannel.deadBackChannelTimerId_); |
| } |
| |
| |
| function assertHasForwardChannel() { |
| assertTrue('Forward channel missing.', hasForwardChannel()); |
| } |
| |
| |
| function assertHasBackChannel() { |
| assertTrue('Back channel missing.', hasBackChannel()); |
| } |
| |
| |
| function testConnect() { |
| connect(); |
| assertEquals(goog.net.BrowserChannel.State.OPENED, |
| browserChannel.getState()); |
| // If the server specifies no version, the client assumes 6 |
| assertEquals(6, browserChannel.channelVersion_); |
| assertFalse(browserChannel.isBuffered()); |
| } |
| |
| function testConnect_backChannelEstablished() { |
| connect(); |
| assertHasBackChannel(); |
| } |
| |
| function testConnect_withServerHostPrefix() { |
| connect(undefined, 'serverHostPrefix'); |
| assertEquals('serverHostPrefix', browserChannel.hostPrefix_); |
| } |
| |
| function testConnect_withClientHostPrefix() { |
| handler.correctHostPrefix = function(hostPrefix) { |
| return 'clientHostPrefix'; |
| }; |
| connect(); |
| assertEquals('clientHostPrefix', browserChannel.hostPrefix_); |
| } |
| |
| function testConnect_overrideServerHostPrefix() { |
| handler.correctHostPrefix = function(hostPrefix) { |
| return 'clientHostPrefix'; |
| }; |
| connect(undefined, 'serverHostPrefix'); |
| assertEquals('clientHostPrefix', browserChannel.hostPrefix_); |
| } |
| |
| function testConnect_withServerVersion() { |
| connect(8); |
| assertEquals(8, browserChannel.channelVersion_); |
| } |
| |
| function testConnect_notOkToMakeRequestForTest() { |
| handler.okToMakeRequest = |
| goog.functions.constant(goog.net.BrowserChannel.Error.NETWORK); |
| browserChannel.connect('/test', '/bind', null); |
| mockClock.tick(0); |
| assertEquals(goog.net.BrowserChannel.State.CLOSED, browserChannel.getState()); |
| } |
| |
| function testConnect_notOkToMakeRequestForBind() { |
| browserChannel.connect('/test', '/bind', null); |
| mockClock.tick(0); |
| completeTestConnection(); |
| handler.okToMakeRequest = |
| goog.functions.constant(goog.net.BrowserChannel.Error.NETWORK); |
| completeForwardChannel(); |
| assertEquals(goog.net.BrowserChannel.State.CLOSED, browserChannel.getState()); |
| } |
| |
| |
| function testSendMap() { |
| connect(); |
| assertEquals(1, numTimingEvents); |
| sendMap('foo', 'bar'); |
| responseVersion7(); |
| assertEquals(2, numTimingEvents); |
| assertEquals('foo:bar', formatArrayOfMaps(deliveredMaps)); |
| } |
| |
| |
| function testSendMap_twice() { |
| connect(); |
| sendMap('foo1', 'bar1'); |
| responseVersion7(); |
| assertEquals('foo1:bar1', formatArrayOfMaps(deliveredMaps)); |
| sendMap('foo2', 'bar2'); |
| responseVersion7(); |
| assertEquals('foo2:bar2', formatArrayOfMaps(deliveredMaps)); |
| } |
| |
| |
| function testSendMap_andReceive() { |
| connect(); |
| sendMap('foo', 'bar'); |
| responseVersion7(); |
| receive('["the server reply"]'); |
| } |
| |
| |
| function testReceive() { |
| connect(); |
| receive('["message from server"]'); |
| assertHasBackChannel(); |
| } |
| |
| |
| function testReceive_twice() { |
| connect(); |
| receive('["message one from server"]'); |
| receive('["message two from server"]'); |
| assertHasBackChannel(); |
| } |
| |
| |
| function testReceive_andSendMap() { |
| connect(); |
| receive('["the server reply"]'); |
| sendMap('foo', 'bar'); |
| responseVersion7(); |
| assertHasBackChannel(); |
| } |
| |
| |
| function testBackChannelRemainsEstablished_afterSingleSendMap() { |
| connect(); |
| |
| sendMap('foo', 'bar'); |
| responseVersion7(); |
| receive('["ack"]'); |
| |
| assertHasBackChannel(); |
| } |
| |
| |
| function testBackChannelRemainsEstablished_afterDoubleSendMap() { |
| connect(); |
| |
| sendMap('foo1', 'bar1'); |
| sendMap('foo2', 'bar2'); |
| responseVersion7(); |
| receive('["ack"]'); |
| |
| // This assertion would fail prior to CL 13302660. |
| assertHasBackChannel(); |
| } |
| |
| |
| function testTimingEvent() { |
| connect(); |
| assertEquals(1, numTimingEvents); |
| sendMap('', ''); |
| assertEquals(1, numTimingEvents); |
| mockClock.tick(20); |
| var expSize = browserChannel.forwardChannelRequest_.getPostData().length; |
| responseVersion7(); |
| |
| assertEquals(2, numTimingEvents); |
| assertEquals(expSize, lastPostSize); |
| assertEquals(20, lastPostRtt); |
| assertEquals(0, lastPostRetryCount); |
| |
| sendMap('abcdefg', '123456'); |
| expSize = browserChannel.forwardChannelRequest_.getPostData().length; |
| responseTimeout(); |
| assertEquals(2, numTimingEvents); |
| mockClock.tick(RETRY_TIME + 1); |
| responseVersion7(); |
| assertEquals(3, numTimingEvents); |
| assertEquals(expSize, lastPostSize); |
| assertEquals(1, lastPostRetryCount); |
| assertEquals(1, lastPostRtt); |
| |
| } |
| |
| |
| /** |
| * Make sure that dropping the forward channel retry limit below the retry count |
| * reports an error, and prevents another request from firing. |
| */ |
| function testSetFailFastWhileWaitingForRetry() { |
| stubTmpnetwork(); |
| |
| connect(); |
| assertEquals(1, numTimingEvents); |
| |
| sendMap('foo', 'bar'); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNotNull(browserChannel.forwardChannelRequest_); |
| assertEquals(0, browserChannel.forwardChannelRetryCount_); |
| |
| // Watchdog timeout. |
| responseTimeout(); |
| assertNotNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(1, browserChannel.forwardChannelRetryCount_); |
| |
| // Almost finish the between-retry timeout. |
| mockClock.tick(RETRY_TIME - 1); |
| assertNotNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(1, browserChannel.forwardChannelRetryCount_); |
| |
| // Setting max retries to 0 should cancel the timer and raise an error. |
| browserChannel.setFailFast(true); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(1, browserChannel.forwardChannelRetryCount_); |
| |
| assertTrue(gotError); |
| assertEquals(0, deliveredMaps.length); |
| // We get the error immediately before starting to ping google.com. |
| // Simulate that timing out. We should get a network error in addition to the |
| // initial failure. |
| gotError = false; |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| assertTrue('No error after tmpnetwork ping timed out.', gotError); |
| |
| // Make sure no more retry timers are firing. |
| mockClock.tick(ALL_DAY_MS); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(1, browserChannel.forwardChannelRetryCount_); |
| assertEquals(1, numTimingEvents); |
| } |
| |
| |
| /** |
| * Make sure that dropping the forward channel retry limit below the retry count |
| * reports an error, and prevents another request from firing. |
| */ |
| function testSetFailFastWhileRetryXhrIsInFlight() { |
| stubTmpnetwork(); |
| |
| connect(); |
| assertEquals(1, numTimingEvents); |
| |
| sendMap('foo', 'bar'); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNotNull(browserChannel.forwardChannelRequest_); |
| assertEquals(0, browserChannel.forwardChannelRetryCount_); |
| |
| // Watchdog timeout. |
| responseTimeout(); |
| assertNotNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(1, browserChannel.forwardChannelRetryCount_); |
| |
| // Wait for the between-retry timeout. |
| mockClock.tick(RETRY_TIME); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNotNull(browserChannel.forwardChannelRequest_); |
| assertEquals(1, browserChannel.forwardChannelRetryCount_); |
| |
| // Simulate a second watchdog timeout. |
| responseTimeout(); |
| assertNotNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(2, browserChannel.forwardChannelRetryCount_); |
| |
| // Wait for another between-retry timeout. |
| mockClock.tick(RETRY_TIME); |
| // Now the third req is in flight. |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNotNull(browserChannel.forwardChannelRequest_); |
| assertEquals(2, browserChannel.forwardChannelRetryCount_); |
| |
| // Set fail fast, killing the request |
| browserChannel.setFailFast(true); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(2, browserChannel.forwardChannelRetryCount_); |
| |
| assertTrue(gotError); |
| // We get the error immediately before starting to ping google.com. |
| // Simulate that timing out. We should get a network error in addition to the |
| gotError = false; |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| assertTrue('No error after tmpnetwork ping timed out.', gotError); |
| |
| // Make sure no more retry timers are firing. |
| mockClock.tick(ALL_DAY_MS); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(2, browserChannel.forwardChannelRetryCount_); |
| assertEquals(1, numTimingEvents); |
| } |
| |
| |
| /** |
| * Makes sure that setting fail fast while not retrying doesn't cause a failure. |
| */ |
| function testSetFailFastAtRetryCount() { |
| stubTmpnetwork(); |
| |
| connect(); |
| assertEquals(1, numTimingEvents); |
| |
| sendMap('foo', 'bar'); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNotNull(browserChannel.forwardChannelRequest_); |
| assertEquals(0, browserChannel.forwardChannelRetryCount_); |
| |
| // Set fail fast. |
| browserChannel.setFailFast(true); |
| // Request should still be alive. |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNotNull(browserChannel.forwardChannelRequest_); |
| assertEquals(0, browserChannel.forwardChannelRetryCount_); |
| |
| // Watchdog timeout. Now we should get an error. |
| responseTimeout(); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(0, browserChannel.forwardChannelRetryCount_); |
| |
| assertTrue(gotError); |
| // We get the error immediately before starting to ping google.com. |
| // Simulate that timing out. We should get a network error in addition to the |
| // initial failure. |
| gotError = false; |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| assertTrue('No error after tmpnetwork ping timed out.', gotError); |
| |
| // Make sure no more retry timers are firing. |
| mockClock.tick(ALL_DAY_MS); |
| assertNull(browserChannel.forwardChannelTimerId_); |
| assertNull(browserChannel.forwardChannelRequest_); |
| assertEquals(0, browserChannel.forwardChannelRetryCount_); |
| assertEquals(1, numTimingEvents); |
| } |
| |
| |
| function testRequestFailedClosesChannel() { |
| stubTmpnetwork(); |
| |
| connect(); |
| assertEquals(1, numTimingEvents); |
| |
| sendMap('foo', 'bar'); |
| responseRequestFailed(); |
| |
| assertEquals('Should be closed immediately after request failed.', |
| goog.net.BrowserChannel.State.CLOSED, browserChannel.getState()); |
| |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| |
| assertEquals('Should remain closed after the ping timeout.', |
| goog.net.BrowserChannel.State.CLOSED, browserChannel.getState()); |
| assertEquals(1, numTimingEvents); |
| } |
| |
| |
| function testStatEventReportedOnlyOnce() { |
| stubTmpnetwork(); |
| |
| connect(); |
| sendMap('foo', 'bar'); |
| numStatEvents = 0; |
| lastStatEvent = null; |
| responseUnknownSessionId(); |
| |
| assertEquals(1, numStatEvents); |
| assertEquals(goog.net.BrowserChannel.Stat.ERROR_OTHER, lastStatEvent); |
| |
| numStatEvents = 0; |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| assertEquals('No new stat events should be reported.', 0, numStatEvents); |
| } |
| |
| |
| function testActiveXBlockedEventReportedOnlyOnce() { |
| stubTmpnetwork(); |
| |
| connectForwardChannel(); |
| numStatEvents = 0; |
| lastStatEvent = null; |
| responseActiveXBlocked(); |
| |
| assertEquals(1, numStatEvents); |
| assertEquals(goog.net.BrowserChannel.Stat.ERROR_OTHER, lastStatEvent); |
| |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| assertEquals('No new stat events should be reported.', 1, numStatEvents); |
| } |
| |
| |
| function testStatEventReportedOnlyOnce_onNetworkUp() { |
| stubTmpnetwork(); |
| |
| connect(); |
| sendMap('foo', 'bar'); |
| numStatEvents = 0; |
| lastStatEvent = null; |
| responseRequestFailed(); |
| |
| assertEquals('No stat event should be reported before we know the reason.', |
| 0, numStatEvents); |
| |
| // Let the ping time out. |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| |
| // Assert we report the correct stat event. |
| assertEquals(1, numStatEvents); |
| assertEquals(goog.net.BrowserChannel.Stat.ERROR_NETWORK, lastStatEvent); |
| } |
| |
| |
| function testStatEventReportedOnlyOnce_onNetworkDown() { |
| stubTmpnetwork(); |
| |
| connect(); |
| sendMap('foo', 'bar'); |
| numStatEvents = 0; |
| lastStatEvent = null; |
| responseRequestFailed(); |
| |
| assertEquals('No stat event should be reported before we know the reason.', |
| 0, numStatEvents); |
| |
| // Wait half the ping timeout period, and then fake the network being up. |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT / 2); |
| browserChannel.testGoogleComCallback_(true); |
| |
| // Assert we report the correct stat event. |
| assertEquals(1, numStatEvents); |
| assertEquals(goog.net.BrowserChannel.Stat.ERROR_OTHER, lastStatEvent); |
| } |
| |
| |
| function testOutgoingMapsAwaitsResponse() { |
| connect(); |
| assertEquals(0, browserChannel.outgoingMaps_.length); |
| |
| sendMap('foo1', 'bar'); |
| assertEquals(0, browserChannel.outgoingMaps_.length); |
| sendMap('foo2', 'bar'); |
| assertEquals(1, browserChannel.outgoingMaps_.length); |
| sendMap('foo3', 'bar'); |
| assertEquals(2, browserChannel.outgoingMaps_.length); |
| sendMap('foo4', 'bar'); |
| assertEquals(3, browserChannel.outgoingMaps_.length); |
| |
| responseVersion7(); |
| // Now the forward channel request is completed and a new started, so all maps |
| // are dequeued from the array of outgoing maps into this new forward request. |
| assertEquals(0, browserChannel.outgoingMaps_.length); |
| } |
| |
| |
| function testUndeliveredMaps_doesNotNotifyWhenSuccessful() { |
| handler.channelClosed = function( |
| channel, opt_pendingMaps, opt_undeliveredMaps) { |
| if (opt_pendingMaps || opt_undeliveredMaps) { |
| fail('No pending or undelivered maps should be reported.'); |
| } |
| }; |
| |
| connect(); |
| sendMap('foo1', 'bar1'); |
| responseVersion7(); |
| sendMap('foo2', 'bar2'); |
| responseVersion7(); |
| disconnect(); |
| } |
| |
| |
| function testUndeliveredMaps_doesNotNotifyIfNothingWasSent() { |
| handler.channelClosed = function( |
| channel, opt_pendingMaps, opt_undeliveredMaps) { |
| if (opt_pendingMaps || opt_undeliveredMaps) { |
| fail('No pending or undelivered maps should be reported.'); |
| } |
| }; |
| |
| connect(); |
| mockClock.tick(ALL_DAY_MS); |
| disconnect(); |
| } |
| |
| |
| function testUndeliveredMaps_clearsPendingMapsAfterNotifying() { |
| connect(); |
| sendMap('foo1', 'bar1'); |
| sendMap('foo2', 'bar2'); |
| sendMap('foo3', 'bar3'); |
| |
| assertEquals(1, browserChannel.pendingMaps_.length); |
| assertEquals(2, browserChannel.outgoingMaps_.length); |
| |
| disconnect(); |
| |
| assertEquals(0, browserChannel.pendingMaps_.length); |
| assertEquals(0, browserChannel.outgoingMaps_.length); |
| } |
| |
| |
| function testUndeliveredMaps_notifiesWithContext() { |
| connect(); |
| |
| // First send two messages that succeed. |
| sendMap('foo1', 'bar1', 'context1'); |
| responseVersion7(); |
| sendMap('foo2', 'bar2', 'context2'); |
| responseVersion7(); |
| |
| // Pretend the server hangs and no longer responds. |
| sendMap('foo3', 'bar3', 'context3'); |
| sendMap('foo4', 'bar4', 'context4'); |
| sendMap('foo5', 'bar5', 'context5'); |
| |
| // Give up. |
| disconnect(); |
| |
| // Assert that we are informed of any undelivered messages; both about |
| // #3 that was sent but which we don't know if the server received, and |
| // #4 and #5 which remain in the outgoing maps and have not yet been sent. |
| assertEquals('foo3:bar3:context3', handler.pendingMapsString); |
| assertEquals('foo4:bar4:context4, foo5:bar5:context5', |
| handler.undeliveredMapsString); |
| } |
| |
| |
| function testUndeliveredMaps_serviceUnavailable() { |
| // Send a few maps, and let one fail. |
| connect(); |
| sendMap('foo1', 'bar1'); |
| responseVersion7(); |
| sendMap('foo2', 'bar2'); |
| responseRequestFailed(); |
| |
| // After a failure, the channel should be closed. |
| disconnect(); |
| |
| assertEquals('foo2:bar2', handler.pendingMapsString); |
| assertEquals('', handler.undeliveredMapsString); |
| } |
| |
| |
| function testUndeliveredMaps_onPingTimeout() { |
| stubTmpnetwork(); |
| |
| connect(); |
| |
| // Send a message. |
| sendMap('foo1', 'bar1'); |
| |
| // Fake REQUEST_FAILED, triggering a ping to check the network. |
| responseRequestFailed(); |
| |
| // Let the ping time out, unsuccessfully. |
| mockClock.tick(goog.net.tmpnetwork.GOOGLECOM_TIMEOUT); |
| |
| // Assert channel is closed. |
| assertEquals(goog.net.BrowserChannel.State.CLOSED, |
| browserChannel.getState()); |
| |
| // Assert that the handler is notified about the undelivered messages. |
| assertEquals('foo1:bar1', handler.pendingMapsString); |
| assertEquals('', handler.undeliveredMapsString); |
| } |
| |
| |
| function testResponseNoBackchannelPostNotBeforeBackchannel() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| |
| mockClock.tick(10); |
| assertFalse(browserChannel.backChannelRequest_.getRequestStartTime() < |
| browserChannel.forwardChannelRequest_.getRequestStartTime()); |
| responseNoBackchannel(); |
| assertNotEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_MISSING, |
| lastStatEvent); |
| } |
| |
| |
| function testResponseNoBackchannel() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| response(-1, 0); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE + 1); |
| sendMap('foo2', 'bar2'); |
| assertTrue(browserChannel.backChannelRequest_.getRequestStartTime() + |
| goog.net.BrowserChannel.RTT_ESTIMATE < |
| browserChannel.forwardChannelRequest_.getRequestStartTime()); |
| responseNoBackchannel(); |
| assertEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_MISSING, lastStatEvent); |
| } |
| |
| |
| function testResponseNoBackchannelWithNoBackchannel() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| assertNull(browserChannel.backChannelTimerId_); |
| browserChannel.backChannelRequest_.cancel(); |
| browserChannel.backChannelRequest_ = null; |
| responseNoBackchannel(); |
| assertEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_MISSING, lastStatEvent); |
| } |
| |
| |
| function testResponseNoBackchannelWithStartTimer() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| |
| browserChannel.backChannelRequest_.cancel(); |
| browserChannel.backChannelRequest_ = null; |
| browserChannel.backChannelTimerId_ = 123; |
| responseNoBackchannel(); |
| assertNotEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_MISSING, |
| lastStatEvent); |
| } |
| |
| |
| function testResponseWithNoArraySent() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| |
| // Send a response as if the server hasn't sent down an array. |
| response(-1, 0); |
| |
| // POST response with an array ID lower than our last received is OK. |
| assertEquals(1, browserChannel.lastArrayId_); |
| assertEquals(-1, browserChannel.lastPostResponseArrayId_); |
| } |
| |
| |
| function testResponseWithArraysMissing() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| assertEquals(-1, browserChannel.lastPostResponseArrayId_); |
| |
| // Send a response as if the server has sent down seven arrays. |
| response(7, 111); |
| |
| assertEquals(1, browserChannel.lastArrayId_); |
| assertEquals(7, browserChannel.lastPostResponseArrayId_); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE * 2); |
| assertEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_DEAD, lastStatEvent); |
| } |
| |
| |
| function testMultipleResponsesWithArraysMissing() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| assertEquals(-1, browserChannel.lastPostResponseArrayId_); |
| |
| // Send a response as if the server has sent down seven arrays. |
| response(7, 111); |
| |
| assertEquals(1, browserChannel.lastArrayId_); |
| assertEquals(7, browserChannel.lastPostResponseArrayId_); |
| sendMap('foo2', 'bar2'); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE); |
| response(8, 119); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE); |
| // The original timer should still fire. |
| assertEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_DEAD, lastStatEvent); |
| } |
| |
| |
| function testOnlyRetryOnceBasedOnResponse() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| assertEquals(-1, browserChannel.lastPostResponseArrayId_); |
| |
| // Send a response as if the server has sent down seven arrays. |
| response(7, 111); |
| |
| assertEquals(1, browserChannel.lastArrayId_); |
| assertEquals(7, browserChannel.lastPostResponseArrayId_); |
| assertTrue(hasDeadBackChannelTimer()); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE * 2); |
| assertEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_DEAD, lastStatEvent); |
| assertEquals(1, browserChannel.backChannelRetryCount_); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE); |
| sendMap('foo2', 'bar2'); |
| assertFalse(hasDeadBackChannelTimer()); |
| response(8, 119); |
| assertFalse(hasDeadBackChannelTimer()); |
| } |
| |
| |
| function testResponseWithArraysMissingAndLiveChannel() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| assertEquals(-1, browserChannel.lastPostResponseArrayId_); |
| |
| // Send a response as if the server has sent down seven arrays. |
| response(7, 111); |
| |
| assertEquals(1, browserChannel.lastArrayId_); |
| assertEquals(7, browserChannel.lastPostResponseArrayId_); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE); |
| assertTrue(hasDeadBackChannelTimer()); |
| receive('["ack"]'); |
| assertFalse(hasDeadBackChannelTimer()); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE); |
| assertNotEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_DEAD, lastStatEvent); |
| } |
| |
| |
| function testResponseWithBigOutstandingData() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| assertEquals(-1, browserChannel.lastPostResponseArrayId_); |
| |
| // Send a response as if the server has sent down seven arrays and 50kbytes. |
| response(7, 50000); |
| |
| assertEquals(1, browserChannel.lastArrayId_); |
| assertEquals(7, browserChannel.lastPostResponseArrayId_); |
| assertFalse(hasDeadBackChannelTimer()); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE * 2); |
| assertNotEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_DEAD, |
| lastStatEvent); |
| } |
| |
| |
| function testResponseInBufferedMode() { |
| connect(8); |
| browserChannel.useChunked_ = false; |
| sendMap('foo1', 'bar1'); |
| assertEquals(-1, browserChannel.lastPostResponseArrayId_); |
| response(7, 111); |
| |
| assertEquals(1, browserChannel.lastArrayId_); |
| assertEquals(7, browserChannel.lastPostResponseArrayId_); |
| assertFalse(hasDeadBackChannelTimer()); |
| mockClock.tick(goog.net.BrowserChannel.RTT_ESTIMATE * 2); |
| assertNotEquals(goog.net.BrowserChannel.Stat.BACKCHANNEL_DEAD, |
| lastStatEvent); |
| } |
| |
| |
| function testResponseWithGarbage() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| browserChannel.onRequestData( |
| browserChannel.forwardChannelRequest_, |
| 'garbage' |
| ); |
| assertEquals(goog.net.BrowserChannel.State.CLOSED, |
| browserChannel.getState()); |
| } |
| |
| |
| function testResponseWithGarbageInArray() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| browserChannel.onRequestData( |
| browserChannel.forwardChannelRequest_, |
| '["garbage"]' |
| ); |
| assertEquals(goog.net.BrowserChannel.State.CLOSED, |
| browserChannel.getState()); |
| } |
| |
| |
| function testResponseWithEvilData() { |
| connect(8); |
| sendMap('foo1', 'bar1'); |
| browserChannel.onRequestData( |
| browserChannel.forwardChannelRequest_, |
| goog.net.BrowserChannel.LAST_ARRAY_ID_RESPONSE_PREFIX + |
| '=<script>evil()\<\/script>&' + |
| goog.net.BrowserChannel.OUTSTANDING_DATA_RESPONSE_PREFIX + |
| '=<script>moreEvil()\<\/script>'); |
| assertEquals(goog.net.BrowserChannel.State.CLOSED, |
| browserChannel.getState()); |
| } |
| |
| |
| function testPathAbsolute() { |
| connect(8, undefined, '/talkgadget'); |
| assertEquals(browserChannel.backChannelUri_.getDomain(), |
| window.location.hostname); |
| assertEquals(browserChannel.forwardChannelUri_.getDomain(), |
| window.location.hostname); |
| } |
| |
| |
| function testPathRelative() { |
| connect(8, undefined, 'talkgadget'); |
| assertEquals(browserChannel.backChannelUri_.getDomain(), |
| window.location.hostname); |
| assertEquals(browserChannel.forwardChannelUri_.getDomain(), |
| window.location.hostname); |
| } |
| |
| |
| function testPathWithHost() { |
| connect(8, undefined, 'https://example.com'); |
| assertEquals(browserChannel.backChannelUri_.getScheme(), 'https'); |
| assertEquals(browserChannel.backChannelUri_.getDomain(), 'example.com'); |
| assertEquals(browserChannel.forwardChannelUri_.getScheme(), 'https'); |
| assertEquals(browserChannel.forwardChannelUri_.getDomain(), 'example.com'); |
| } |
| |
| function testCreateXhrIo() { |
| var xhr = browserChannel.createXhrIo(null); |
| assertFalse(xhr.getWithCredentials()); |
| |
| assertThrows( |
| 'Error connection to different host without CORS', |
| goog.bind(browserChannel.createXhrIo, browserChannel, 'some_host')); |
| |
| browserChannel.setSupportsCrossDomainXhrs(true); |
| |
| xhr = browserChannel.createXhrIo(null); |
| assertTrue(xhr.getWithCredentials()); |
| |
| xhr = browserChannel.createXhrIo('some_host'); |
| assertTrue(xhr.getWithCredentials()); |
| } |
| |
| function testSetParser() { |
| var recordUnsafeParse = goog.testing.recordFunction( |
| goog.json.unsafeParse); |
| var parser = {}; |
| parser.parse = recordUnsafeParse; |
| browserChannel.setParser(parser); |
| |
| connect(); |
| assertEquals(3, recordUnsafeParse.getCallCount()); |
| |
| var call3 = recordUnsafeParse.popLastCall(); |
| var call2 = recordUnsafeParse.popLastCall(); |
| var call1 = recordUnsafeParse.popLastCall(); |
| |
| assertEquals(1, call1.getArguments().length); |
| assertEquals('["b"]', call1.getArgument(0)); |
| |
| assertEquals(1, call2.getArguments().length); |
| assertEquals('[[0,["c","1234567890ABCDEF",null]]]', call2.getArgument(0)); |
| |
| assertEquals(1, call3.getArguments().length); |
| assertEquals('[[1,["foo"]]]', call3.getArgument(0)); |
| } |