michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: */ michael@0: michael@0: /* General MAR File Download Tests */ michael@0: michael@0: const INC_CONTRACT_ID = "@mozilla.org/network/incremental-download;1"; michael@0: michael@0: var gIncrementalDownloadClassID, gIncOldFactory; michael@0: // gIncrementalDownloadErrorType is used to loop through each of the connection michael@0: // error types in the Mock incremental downloader. michael@0: var gIncrementalDownloadErrorType = 0; michael@0: michael@0: var gNextRunFunc; michael@0: var gExpectedStatusResult; michael@0: michael@0: function run_test() { michael@0: setupTestCommon(); michael@0: michael@0: logTestInfo("testing mar downloads, mar hash verification, and " + michael@0: "mar download interrupted recovery"); michael@0: michael@0: Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false); michael@0: // The HTTP server is only used for the mar file downloads since it is slow michael@0: start_httpserver(); michael@0: setUpdateURLOverride(gURLData + "update.xml"); michael@0: // The mock XMLHttpRequest is MUCH faster michael@0: overrideXHR(callHandleEvent); michael@0: standardInit(); michael@0: do_execute_soon(run_test_pt1); michael@0: } michael@0: michael@0: // The HttpServer must be stopped before calling do_test_finished michael@0: function finish_test() { michael@0: stop_httpserver(doTestFinish); michael@0: } michael@0: michael@0: function end_test() { michael@0: cleanupMockIncrementalDownload(); michael@0: } michael@0: michael@0: // Callback function used by the custom XMLHttpRequest implementation to michael@0: // call the nsIDOMEventListener's handleEvent method for onload. michael@0: function callHandleEvent() { michael@0: gXHR.status = 400; michael@0: gXHR.responseText = gResponseBody; michael@0: try { michael@0: var parser = AUS_Cc["@mozilla.org/xmlextras/domparser;1"]. michael@0: createInstance(AUS_Ci.nsIDOMParser); michael@0: gXHR.responseXML = parser.parseFromString(gResponseBody, "application/xml"); michael@0: } catch (e) { michael@0: } michael@0: var e = { target: gXHR }; michael@0: gXHR.onload(e); michael@0: } michael@0: michael@0: // Helper function for testing mar downloads that have the correct size michael@0: // specified in the update xml. michael@0: function run_test_helper_pt1(aMsg, aExpectedStatusResult, aNextRunFunc) { michael@0: gUpdates = null; michael@0: gUpdateCount = null; michael@0: gStatusResult = null; michael@0: gCheckFunc = check_test_helper_pt1_1; michael@0: gNextRunFunc = aNextRunFunc; michael@0: gExpectedStatusResult = aExpectedStatusResult; michael@0: logTestInfo(aMsg, Components.stack.caller); michael@0: gUpdateChecker.checkForUpdates(updateCheckListener, true); michael@0: } michael@0: michael@0: function check_test_helper_pt1_1() { michael@0: do_check_eq(gUpdateCount, 1); michael@0: gCheckFunc = check_test_helper_pt1_2; michael@0: var bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount); michael@0: var state = gAUS.downloadUpdate(bestUpdate, false); michael@0: if (state == STATE_NONE || state == STATE_FAILED) michael@0: do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state); michael@0: gAUS.addDownloadListener(downloadListener); michael@0: } michael@0: michael@0: function check_test_helper_pt1_2() { michael@0: do_check_eq(gStatusResult, gExpectedStatusResult); michael@0: gAUS.removeDownloadListener(downloadListener); michael@0: gNextRunFunc(); michael@0: } michael@0: michael@0: function setResponseBody(aHashFunction, aHashValue, aSize) { michael@0: var patches = getRemotePatchString(null, null, michael@0: aHashFunction, aHashValue, aSize); michael@0: var updates = getRemoteUpdateString(patches); michael@0: gResponseBody = getRemoteUpdatesXMLString(updates); michael@0: } michael@0: michael@0: var newFactory = { michael@0: createInstance: function(aOuter, aIID) { michael@0: if (aOuter) michael@0: throw Components.results.NS_ERROR_NO_AGGREGATION; michael@0: return new IncrementalDownload().QueryInterface(aIID); michael@0: }, michael@0: lockFactory: function(aLock) { michael@0: throw Components.results.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIFactory]) michael@0: }; michael@0: michael@0: function initMockIncrementalDownload() { michael@0: var registrar = AUS_Cm.QueryInterface(AUS_Ci.nsIComponentRegistrar); michael@0: gIncrementalDownloadClassID = registrar.contractIDToCID(INC_CONTRACT_ID); michael@0: gIncOldFactory = AUS_Cm.getClassObject(AUS_Cc[INC_CONTRACT_ID], michael@0: AUS_Ci.nsIFactory); michael@0: registrar.unregisterFactory(gIncrementalDownloadClassID, gIncOldFactory); michael@0: var components = [IncrementalDownload]; michael@0: registrar.registerFactory(gIncrementalDownloadClassID, "", michael@0: INC_CONTRACT_ID, newFactory); michael@0: } michael@0: michael@0: function cleanupMockIncrementalDownload() { michael@0: if (gIncOldFactory) { michael@0: var registrar = AUS_Cm.QueryInterface(AUS_Ci.nsIComponentRegistrar); michael@0: registrar.unregisterFactory(gIncrementalDownloadClassID, newFactory); michael@0: registrar.registerFactory(gIncrementalDownloadClassID, "", michael@0: INC_CONTRACT_ID, gIncOldFactory); michael@0: } michael@0: gIncOldFactory = null; michael@0: } michael@0: michael@0: /* This Mock incremental downloader is used to verify that connection michael@0: * interrupts work correctly in updater code. The implementation of michael@0: * the mock incremental downloader is very simple, it simply copies michael@0: * the file to the destination location. michael@0: */ michael@0: michael@0: function IncrementalDownload() { michael@0: this.wrappedJSObject = this; michael@0: } michael@0: michael@0: IncrementalDownload.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIIncrementalDownload]), michael@0: michael@0: /* nsIIncrementalDownload */ michael@0: init: function(uri, file, chunkSize, intervalInSeconds) { michael@0: this._destination = file; michael@0: this._URI = uri; michael@0: this._finalURI = uri; michael@0: }, michael@0: michael@0: start: function(observer, ctxt) { michael@0: var tm = Components.classes["@mozilla.org/thread-manager;1"]. michael@0: getService(AUS_Ci.nsIThreadManager); michael@0: // Do the actual operation async to give a chance for observers michael@0: // to add themselves. michael@0: tm.mainThread.dispatch(function() { michael@0: this._observer = observer.QueryInterface(AUS_Ci.nsIRequestObserver); michael@0: this._ctxt = ctxt; michael@0: this._observer.onStartRequest(this, this.ctxt); michael@0: let mar = getTestDirFile(FILE_SIMPLE_MAR); michael@0: mar.copyTo(this._destination.parent, this._destination.leafName); michael@0: var status = AUS_Cr.NS_OK michael@0: switch (gIncrementalDownloadErrorType++) { michael@0: case 0: michael@0: status = AUS_Cr.NS_ERROR_NET_RESET; michael@0: break; michael@0: case 1: michael@0: status = AUS_Cr.NS_ERROR_CONNECTION_REFUSED; michael@0: break; michael@0: case 2: michael@0: status = AUS_Cr.NS_ERROR_NET_RESET; michael@0: break; michael@0: case 3: michael@0: status = AUS_Cr.NS_OK; michael@0: break; michael@0: case 4: michael@0: status = AUS_Cr.NS_ERROR_OFFLINE; michael@0: // After we report offline, we want to eventually show offline michael@0: // status being changed to online. michael@0: var tm = Components.classes["@mozilla.org/thread-manager;1"]. michael@0: getService(AUS_Ci.nsIThreadManager); michael@0: tm.mainThread.dispatch(function() { michael@0: Services.obs.notifyObservers(gAUS, michael@0: "network:offline-status-changed", michael@0: "online"); michael@0: }, AUS_Ci.nsIThread.DISPATCH_NORMAL); michael@0: break; michael@0: } michael@0: this._observer.onStopRequest(this, this._ctxt, status); michael@0: }.bind(this), AUS_Ci.nsIThread.DISPATCH_NORMAL); michael@0: }, michael@0: michael@0: get URI() { michael@0: return this._URI; michael@0: }, michael@0: michael@0: get currentSize() { michael@0: throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: michael@0: get destination() { michael@0: return this._destination; michael@0: }, michael@0: michael@0: get finalURI() { michael@0: return this._finalURI; michael@0: }, michael@0: michael@0: get totalSize() { michael@0: throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: michael@0: /* nsIRequest */ michael@0: cancel: function(aStatus) { michael@0: throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: suspend: function() { michael@0: throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: isPending: function() { michael@0: throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: _loadFlags: 0, michael@0: get loadFlags() { michael@0: return this._loadFlags; michael@0: }, michael@0: set loadFlags(val) { michael@0: this._loadFlags = val; michael@0: }, michael@0: michael@0: _loadGroup: null, michael@0: get loadGroup() { michael@0: return this._loadGroup; michael@0: }, michael@0: set loadGroup(val) { michael@0: this._loadGroup = val; michael@0: }, michael@0: michael@0: _name: "", michael@0: get name() { michael@0: return this._name; michael@0: }, michael@0: michael@0: _status: 0, michael@0: get status() { michael@0: return this._status; michael@0: } michael@0: } michael@0: michael@0: // Test disconnecting during an update michael@0: function run_test_pt1() { michael@0: initMockIncrementalDownload(); michael@0: setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); michael@0: run_test_helper_pt1("mar download with connection interruption", michael@0: AUS_Cr.NS_OK, run_test_pt2); michael@0: } michael@0: michael@0: // Test disconnecting during an update michael@0: function run_test_pt2() { michael@0: gIncrementalDownloadErrorType = 0; michael@0: Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_ERRORS, 2); michael@0: Services.prefs.setIntPref(PREF_APP_UPDATE_RETRY_TIMEOUT, 0); michael@0: setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); michael@0: michael@0: var expectedResult; michael@0: if (IS_TOOLKIT_GONK) { michael@0: // Gonk treats interrupted downloads differently. For gonk, if the state michael@0: // is pending, this means that the download has completed and only the michael@0: // staging needs to occur. So gonk will skip the download portion which michael@0: // results in an NS_OK return. michael@0: expectedResult = AUS_Cr.NS_OK; michael@0: } else { michael@0: expectedResult = AUS_Cr.NS_ERROR_NET_RESET; michael@0: } michael@0: run_test_helper_pt1("mar download with connection interruption without recovery", michael@0: expectedResult, run_test_pt3); michael@0: } michael@0: michael@0: // Test entering offline mode while downloading michael@0: function run_test_pt3() { michael@0: gIncrementalDownloadErrorType = 4; michael@0: setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); michael@0: run_test_helper_pt1("mar download with offline mode", michael@0: AUS_Cr.NS_OK, finish_test); michael@0: }