1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,281 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + */ 1.8 + 1.9 +/* General MAR File Download Tests */ 1.10 + 1.11 +const INC_CONTRACT_ID = "@mozilla.org/network/incremental-download;1"; 1.12 + 1.13 +var gIncrementalDownloadClassID, gIncOldFactory; 1.14 +// gIncrementalDownloadErrorType is used to loop through each of the connection 1.15 +// error types in the Mock incremental downloader. 1.16 +var gIncrementalDownloadErrorType = 0; 1.17 + 1.18 +var gNextRunFunc; 1.19 +var gExpectedStatusResult; 1.20 + 1.21 +function run_test() { 1.22 + setupTestCommon(); 1.23 + 1.24 + logTestInfo("testing mar downloads, mar hash verification, and " + 1.25 + "mar download interrupted recovery"); 1.26 + 1.27 + Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false); 1.28 + // The HTTP server is only used for the mar file downloads since it is slow 1.29 + start_httpserver(); 1.30 + setUpdateURLOverride(gURLData + "update.xml"); 1.31 + // The mock XMLHttpRequest is MUCH faster 1.32 + overrideXHR(callHandleEvent); 1.33 + standardInit(); 1.34 + do_execute_soon(run_test_pt1); 1.35 +} 1.36 + 1.37 +// The HttpServer must be stopped before calling do_test_finished 1.38 +function finish_test() { 1.39 + stop_httpserver(doTestFinish); 1.40 +} 1.41 + 1.42 +function end_test() { 1.43 + cleanupMockIncrementalDownload(); 1.44 +} 1.45 + 1.46 +// Callback function used by the custom XMLHttpRequest implementation to 1.47 +// call the nsIDOMEventListener's handleEvent method for onload. 1.48 +function callHandleEvent() { 1.49 + gXHR.status = 400; 1.50 + gXHR.responseText = gResponseBody; 1.51 + try { 1.52 + var parser = AUS_Cc["@mozilla.org/xmlextras/domparser;1"]. 1.53 + createInstance(AUS_Ci.nsIDOMParser); 1.54 + gXHR.responseXML = parser.parseFromString(gResponseBody, "application/xml"); 1.55 + } catch (e) { 1.56 + } 1.57 + var e = { target: gXHR }; 1.58 + gXHR.onload(e); 1.59 +} 1.60 + 1.61 +// Helper function for testing mar downloads that have the correct size 1.62 +// specified in the update xml. 1.63 +function run_test_helper_pt1(aMsg, aExpectedStatusResult, aNextRunFunc) { 1.64 + gUpdates = null; 1.65 + gUpdateCount = null; 1.66 + gStatusResult = null; 1.67 + gCheckFunc = check_test_helper_pt1_1; 1.68 + gNextRunFunc = aNextRunFunc; 1.69 + gExpectedStatusResult = aExpectedStatusResult; 1.70 + logTestInfo(aMsg, Components.stack.caller); 1.71 + gUpdateChecker.checkForUpdates(updateCheckListener, true); 1.72 +} 1.73 + 1.74 +function check_test_helper_pt1_1() { 1.75 + do_check_eq(gUpdateCount, 1); 1.76 + gCheckFunc = check_test_helper_pt1_2; 1.77 + var bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount); 1.78 + var state = gAUS.downloadUpdate(bestUpdate, false); 1.79 + if (state == STATE_NONE || state == STATE_FAILED) 1.80 + do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state); 1.81 + gAUS.addDownloadListener(downloadListener); 1.82 +} 1.83 + 1.84 +function check_test_helper_pt1_2() { 1.85 + do_check_eq(gStatusResult, gExpectedStatusResult); 1.86 + gAUS.removeDownloadListener(downloadListener); 1.87 + gNextRunFunc(); 1.88 +} 1.89 + 1.90 +function setResponseBody(aHashFunction, aHashValue, aSize) { 1.91 + var patches = getRemotePatchString(null, null, 1.92 + aHashFunction, aHashValue, aSize); 1.93 + var updates = getRemoteUpdateString(patches); 1.94 + gResponseBody = getRemoteUpdatesXMLString(updates); 1.95 +} 1.96 + 1.97 +var newFactory = { 1.98 + createInstance: function(aOuter, aIID) { 1.99 + if (aOuter) 1.100 + throw Components.results.NS_ERROR_NO_AGGREGATION; 1.101 + return new IncrementalDownload().QueryInterface(aIID); 1.102 + }, 1.103 + lockFactory: function(aLock) { 1.104 + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; 1.105 + }, 1.106 + QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIFactory]) 1.107 +}; 1.108 + 1.109 +function initMockIncrementalDownload() { 1.110 + var registrar = AUS_Cm.QueryInterface(AUS_Ci.nsIComponentRegistrar); 1.111 + gIncrementalDownloadClassID = registrar.contractIDToCID(INC_CONTRACT_ID); 1.112 + gIncOldFactory = AUS_Cm.getClassObject(AUS_Cc[INC_CONTRACT_ID], 1.113 + AUS_Ci.nsIFactory); 1.114 + registrar.unregisterFactory(gIncrementalDownloadClassID, gIncOldFactory); 1.115 + var components = [IncrementalDownload]; 1.116 + registrar.registerFactory(gIncrementalDownloadClassID, "", 1.117 + INC_CONTRACT_ID, newFactory); 1.118 +} 1.119 + 1.120 +function cleanupMockIncrementalDownload() { 1.121 + if (gIncOldFactory) { 1.122 + var registrar = AUS_Cm.QueryInterface(AUS_Ci.nsIComponentRegistrar); 1.123 + registrar.unregisterFactory(gIncrementalDownloadClassID, newFactory); 1.124 + registrar.registerFactory(gIncrementalDownloadClassID, "", 1.125 + INC_CONTRACT_ID, gIncOldFactory); 1.126 + } 1.127 + gIncOldFactory = null; 1.128 +} 1.129 + 1.130 +/* This Mock incremental downloader is used to verify that connection 1.131 + * interrupts work correctly in updater code. The implementation of 1.132 + * the mock incremental downloader is very simple, it simply copies 1.133 + * the file to the destination location. 1.134 + */ 1.135 + 1.136 +function IncrementalDownload() { 1.137 + this.wrappedJSObject = this; 1.138 +} 1.139 + 1.140 +IncrementalDownload.prototype = { 1.141 + QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIIncrementalDownload]), 1.142 + 1.143 + /* nsIIncrementalDownload */ 1.144 + init: function(uri, file, chunkSize, intervalInSeconds) { 1.145 + this._destination = file; 1.146 + this._URI = uri; 1.147 + this._finalURI = uri; 1.148 + }, 1.149 + 1.150 + start: function(observer, ctxt) { 1.151 + var tm = Components.classes["@mozilla.org/thread-manager;1"]. 1.152 + getService(AUS_Ci.nsIThreadManager); 1.153 + // Do the actual operation async to give a chance for observers 1.154 + // to add themselves. 1.155 + tm.mainThread.dispatch(function() { 1.156 + this._observer = observer.QueryInterface(AUS_Ci.nsIRequestObserver); 1.157 + this._ctxt = ctxt; 1.158 + this._observer.onStartRequest(this, this.ctxt); 1.159 + let mar = getTestDirFile(FILE_SIMPLE_MAR); 1.160 + mar.copyTo(this._destination.parent, this._destination.leafName); 1.161 + var status = AUS_Cr.NS_OK 1.162 + switch (gIncrementalDownloadErrorType++) { 1.163 + case 0: 1.164 + status = AUS_Cr.NS_ERROR_NET_RESET; 1.165 + break; 1.166 + case 1: 1.167 + status = AUS_Cr.NS_ERROR_CONNECTION_REFUSED; 1.168 + break; 1.169 + case 2: 1.170 + status = AUS_Cr.NS_ERROR_NET_RESET; 1.171 + break; 1.172 + case 3: 1.173 + status = AUS_Cr.NS_OK; 1.174 + break; 1.175 + case 4: 1.176 + status = AUS_Cr.NS_ERROR_OFFLINE; 1.177 + // After we report offline, we want to eventually show offline 1.178 + // status being changed to online. 1.179 + var tm = Components.classes["@mozilla.org/thread-manager;1"]. 1.180 + getService(AUS_Ci.nsIThreadManager); 1.181 + tm.mainThread.dispatch(function() { 1.182 + Services.obs.notifyObservers(gAUS, 1.183 + "network:offline-status-changed", 1.184 + "online"); 1.185 + }, AUS_Ci.nsIThread.DISPATCH_NORMAL); 1.186 + break; 1.187 + } 1.188 + this._observer.onStopRequest(this, this._ctxt, status); 1.189 + }.bind(this), AUS_Ci.nsIThread.DISPATCH_NORMAL); 1.190 + }, 1.191 + 1.192 + get URI() { 1.193 + return this._URI; 1.194 + }, 1.195 + 1.196 + get currentSize() { 1.197 + throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; 1.198 + }, 1.199 + 1.200 + get destination() { 1.201 + return this._destination; 1.202 + }, 1.203 + 1.204 + get finalURI() { 1.205 + return this._finalURI; 1.206 + }, 1.207 + 1.208 + get totalSize() { 1.209 + throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; 1.210 + }, 1.211 + 1.212 + /* nsIRequest */ 1.213 + cancel: function(aStatus) { 1.214 + throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; 1.215 + }, 1.216 + suspend: function() { 1.217 + throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; 1.218 + }, 1.219 + isPending: function() { 1.220 + throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; 1.221 + }, 1.222 + _loadFlags: 0, 1.223 + get loadFlags() { 1.224 + return this._loadFlags; 1.225 + }, 1.226 + set loadFlags(val) { 1.227 + this._loadFlags = val; 1.228 + }, 1.229 + 1.230 + _loadGroup: null, 1.231 + get loadGroup() { 1.232 + return this._loadGroup; 1.233 + }, 1.234 + set loadGroup(val) { 1.235 + this._loadGroup = val; 1.236 + }, 1.237 + 1.238 + _name: "", 1.239 + get name() { 1.240 + return this._name; 1.241 + }, 1.242 + 1.243 + _status: 0, 1.244 + get status() { 1.245 + return this._status; 1.246 + } 1.247 +} 1.248 + 1.249 +// Test disconnecting during an update 1.250 +function run_test_pt1() { 1.251 + initMockIncrementalDownload(); 1.252 + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); 1.253 + run_test_helper_pt1("mar download with connection interruption", 1.254 + AUS_Cr.NS_OK, run_test_pt2); 1.255 +} 1.256 + 1.257 +// Test disconnecting during an update 1.258 +function run_test_pt2() { 1.259 + gIncrementalDownloadErrorType = 0; 1.260 + Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_ERRORS, 2); 1.261 + Services.prefs.setIntPref(PREF_APP_UPDATE_RETRY_TIMEOUT, 0); 1.262 + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); 1.263 + 1.264 + var expectedResult; 1.265 + if (IS_TOOLKIT_GONK) { 1.266 + // Gonk treats interrupted downloads differently. For gonk, if the state 1.267 + // is pending, this means that the download has completed and only the 1.268 + // staging needs to occur. So gonk will skip the download portion which 1.269 + // results in an NS_OK return. 1.270 + expectedResult = AUS_Cr.NS_OK; 1.271 + } else { 1.272 + expectedResult = AUS_Cr.NS_ERROR_NET_RESET; 1.273 + } 1.274 + run_test_helper_pt1("mar download with connection interruption without recovery", 1.275 + expectedResult, run_test_pt3); 1.276 +} 1.277 + 1.278 +// Test entering offline mode while downloading 1.279 +function run_test_pt3() { 1.280 + gIncrementalDownloadErrorType = 4; 1.281 + setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); 1.282 + run_test_helper_pt1("mar download with offline mode", 1.283 + AUS_Cr.NS_OK, finish_test); 1.284 +}