|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 */ |
|
5 |
|
6 /* General MAR File Download Tests */ |
|
7 |
|
8 const INC_CONTRACT_ID = "@mozilla.org/network/incremental-download;1"; |
|
9 |
|
10 var gIncrementalDownloadClassID, gIncOldFactory; |
|
11 // gIncrementalDownloadErrorType is used to loop through each of the connection |
|
12 // error types in the Mock incremental downloader. |
|
13 var gIncrementalDownloadErrorType = 0; |
|
14 |
|
15 var gNextRunFunc; |
|
16 var gExpectedStatusResult; |
|
17 |
|
18 function run_test() { |
|
19 setupTestCommon(); |
|
20 |
|
21 logTestInfo("testing mar downloads, mar hash verification, and " + |
|
22 "mar download interrupted recovery"); |
|
23 |
|
24 Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false); |
|
25 // The HTTP server is only used for the mar file downloads since it is slow |
|
26 start_httpserver(); |
|
27 setUpdateURLOverride(gURLData + "update.xml"); |
|
28 // The mock XMLHttpRequest is MUCH faster |
|
29 overrideXHR(callHandleEvent); |
|
30 standardInit(); |
|
31 do_execute_soon(run_test_pt1); |
|
32 } |
|
33 |
|
34 // The HttpServer must be stopped before calling do_test_finished |
|
35 function finish_test() { |
|
36 stop_httpserver(doTestFinish); |
|
37 } |
|
38 |
|
39 function end_test() { |
|
40 cleanupMockIncrementalDownload(); |
|
41 } |
|
42 |
|
43 // Callback function used by the custom XMLHttpRequest implementation to |
|
44 // call the nsIDOMEventListener's handleEvent method for onload. |
|
45 function callHandleEvent() { |
|
46 gXHR.status = 400; |
|
47 gXHR.responseText = gResponseBody; |
|
48 try { |
|
49 var parser = AUS_Cc["@mozilla.org/xmlextras/domparser;1"]. |
|
50 createInstance(AUS_Ci.nsIDOMParser); |
|
51 gXHR.responseXML = parser.parseFromString(gResponseBody, "application/xml"); |
|
52 } catch (e) { |
|
53 } |
|
54 var e = { target: gXHR }; |
|
55 gXHR.onload(e); |
|
56 } |
|
57 |
|
58 // Helper function for testing mar downloads that have the correct size |
|
59 // specified in the update xml. |
|
60 function run_test_helper_pt1(aMsg, aExpectedStatusResult, aNextRunFunc) { |
|
61 gUpdates = null; |
|
62 gUpdateCount = null; |
|
63 gStatusResult = null; |
|
64 gCheckFunc = check_test_helper_pt1_1; |
|
65 gNextRunFunc = aNextRunFunc; |
|
66 gExpectedStatusResult = aExpectedStatusResult; |
|
67 logTestInfo(aMsg, Components.stack.caller); |
|
68 gUpdateChecker.checkForUpdates(updateCheckListener, true); |
|
69 } |
|
70 |
|
71 function check_test_helper_pt1_1() { |
|
72 do_check_eq(gUpdateCount, 1); |
|
73 gCheckFunc = check_test_helper_pt1_2; |
|
74 var bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount); |
|
75 var state = gAUS.downloadUpdate(bestUpdate, false); |
|
76 if (state == STATE_NONE || state == STATE_FAILED) |
|
77 do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state); |
|
78 gAUS.addDownloadListener(downloadListener); |
|
79 } |
|
80 |
|
81 function check_test_helper_pt1_2() { |
|
82 do_check_eq(gStatusResult, gExpectedStatusResult); |
|
83 gAUS.removeDownloadListener(downloadListener); |
|
84 gNextRunFunc(); |
|
85 } |
|
86 |
|
87 function setResponseBody(aHashFunction, aHashValue, aSize) { |
|
88 var patches = getRemotePatchString(null, null, |
|
89 aHashFunction, aHashValue, aSize); |
|
90 var updates = getRemoteUpdateString(patches); |
|
91 gResponseBody = getRemoteUpdatesXMLString(updates); |
|
92 } |
|
93 |
|
94 var newFactory = { |
|
95 createInstance: function(aOuter, aIID) { |
|
96 if (aOuter) |
|
97 throw Components.results.NS_ERROR_NO_AGGREGATION; |
|
98 return new IncrementalDownload().QueryInterface(aIID); |
|
99 }, |
|
100 lockFactory: function(aLock) { |
|
101 throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
|
102 }, |
|
103 QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIFactory]) |
|
104 }; |
|
105 |
|
106 function initMockIncrementalDownload() { |
|
107 var registrar = AUS_Cm.QueryInterface(AUS_Ci.nsIComponentRegistrar); |
|
108 gIncrementalDownloadClassID = registrar.contractIDToCID(INC_CONTRACT_ID); |
|
109 gIncOldFactory = AUS_Cm.getClassObject(AUS_Cc[INC_CONTRACT_ID], |
|
110 AUS_Ci.nsIFactory); |
|
111 registrar.unregisterFactory(gIncrementalDownloadClassID, gIncOldFactory); |
|
112 var components = [IncrementalDownload]; |
|
113 registrar.registerFactory(gIncrementalDownloadClassID, "", |
|
114 INC_CONTRACT_ID, newFactory); |
|
115 } |
|
116 |
|
117 function cleanupMockIncrementalDownload() { |
|
118 if (gIncOldFactory) { |
|
119 var registrar = AUS_Cm.QueryInterface(AUS_Ci.nsIComponentRegistrar); |
|
120 registrar.unregisterFactory(gIncrementalDownloadClassID, newFactory); |
|
121 registrar.registerFactory(gIncrementalDownloadClassID, "", |
|
122 INC_CONTRACT_ID, gIncOldFactory); |
|
123 } |
|
124 gIncOldFactory = null; |
|
125 } |
|
126 |
|
127 /* This Mock incremental downloader is used to verify that connection |
|
128 * interrupts work correctly in updater code. The implementation of |
|
129 * the mock incremental downloader is very simple, it simply copies |
|
130 * the file to the destination location. |
|
131 */ |
|
132 |
|
133 function IncrementalDownload() { |
|
134 this.wrappedJSObject = this; |
|
135 } |
|
136 |
|
137 IncrementalDownload.prototype = { |
|
138 QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIIncrementalDownload]), |
|
139 |
|
140 /* nsIIncrementalDownload */ |
|
141 init: function(uri, file, chunkSize, intervalInSeconds) { |
|
142 this._destination = file; |
|
143 this._URI = uri; |
|
144 this._finalURI = uri; |
|
145 }, |
|
146 |
|
147 start: function(observer, ctxt) { |
|
148 var tm = Components.classes["@mozilla.org/thread-manager;1"]. |
|
149 getService(AUS_Ci.nsIThreadManager); |
|
150 // Do the actual operation async to give a chance for observers |
|
151 // to add themselves. |
|
152 tm.mainThread.dispatch(function() { |
|
153 this._observer = observer.QueryInterface(AUS_Ci.nsIRequestObserver); |
|
154 this._ctxt = ctxt; |
|
155 this._observer.onStartRequest(this, this.ctxt); |
|
156 let mar = getTestDirFile(FILE_SIMPLE_MAR); |
|
157 mar.copyTo(this._destination.parent, this._destination.leafName); |
|
158 var status = AUS_Cr.NS_OK |
|
159 switch (gIncrementalDownloadErrorType++) { |
|
160 case 0: |
|
161 status = AUS_Cr.NS_ERROR_NET_RESET; |
|
162 break; |
|
163 case 1: |
|
164 status = AUS_Cr.NS_ERROR_CONNECTION_REFUSED; |
|
165 break; |
|
166 case 2: |
|
167 status = AUS_Cr.NS_ERROR_NET_RESET; |
|
168 break; |
|
169 case 3: |
|
170 status = AUS_Cr.NS_OK; |
|
171 break; |
|
172 case 4: |
|
173 status = AUS_Cr.NS_ERROR_OFFLINE; |
|
174 // After we report offline, we want to eventually show offline |
|
175 // status being changed to online. |
|
176 var tm = Components.classes["@mozilla.org/thread-manager;1"]. |
|
177 getService(AUS_Ci.nsIThreadManager); |
|
178 tm.mainThread.dispatch(function() { |
|
179 Services.obs.notifyObservers(gAUS, |
|
180 "network:offline-status-changed", |
|
181 "online"); |
|
182 }, AUS_Ci.nsIThread.DISPATCH_NORMAL); |
|
183 break; |
|
184 } |
|
185 this._observer.onStopRequest(this, this._ctxt, status); |
|
186 }.bind(this), AUS_Ci.nsIThread.DISPATCH_NORMAL); |
|
187 }, |
|
188 |
|
189 get URI() { |
|
190 return this._URI; |
|
191 }, |
|
192 |
|
193 get currentSize() { |
|
194 throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
195 }, |
|
196 |
|
197 get destination() { |
|
198 return this._destination; |
|
199 }, |
|
200 |
|
201 get finalURI() { |
|
202 return this._finalURI; |
|
203 }, |
|
204 |
|
205 get totalSize() { |
|
206 throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
207 }, |
|
208 |
|
209 /* nsIRequest */ |
|
210 cancel: function(aStatus) { |
|
211 throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
212 }, |
|
213 suspend: function() { |
|
214 throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
215 }, |
|
216 isPending: function() { |
|
217 throw AUS_Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
218 }, |
|
219 _loadFlags: 0, |
|
220 get loadFlags() { |
|
221 return this._loadFlags; |
|
222 }, |
|
223 set loadFlags(val) { |
|
224 this._loadFlags = val; |
|
225 }, |
|
226 |
|
227 _loadGroup: null, |
|
228 get loadGroup() { |
|
229 return this._loadGroup; |
|
230 }, |
|
231 set loadGroup(val) { |
|
232 this._loadGroup = val; |
|
233 }, |
|
234 |
|
235 _name: "", |
|
236 get name() { |
|
237 return this._name; |
|
238 }, |
|
239 |
|
240 _status: 0, |
|
241 get status() { |
|
242 return this._status; |
|
243 } |
|
244 } |
|
245 |
|
246 // Test disconnecting during an update |
|
247 function run_test_pt1() { |
|
248 initMockIncrementalDownload(); |
|
249 setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); |
|
250 run_test_helper_pt1("mar download with connection interruption", |
|
251 AUS_Cr.NS_OK, run_test_pt2); |
|
252 } |
|
253 |
|
254 // Test disconnecting during an update |
|
255 function run_test_pt2() { |
|
256 gIncrementalDownloadErrorType = 0; |
|
257 Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_ERRORS, 2); |
|
258 Services.prefs.setIntPref(PREF_APP_UPDATE_RETRY_TIMEOUT, 0); |
|
259 setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); |
|
260 |
|
261 var expectedResult; |
|
262 if (IS_TOOLKIT_GONK) { |
|
263 // Gonk treats interrupted downloads differently. For gonk, if the state |
|
264 // is pending, this means that the download has completed and only the |
|
265 // staging needs to occur. So gonk will skip the download portion which |
|
266 // results in an NS_OK return. |
|
267 expectedResult = AUS_Cr.NS_OK; |
|
268 } else { |
|
269 expectedResult = AUS_Cr.NS_ERROR_NET_RESET; |
|
270 } |
|
271 run_test_helper_pt1("mar download with connection interruption without recovery", |
|
272 expectedResult, run_test_pt3); |
|
273 } |
|
274 |
|
275 // Test entering offline mode while downloading |
|
276 function run_test_pt3() { |
|
277 gIncrementalDownloadErrorType = 4; |
|
278 setResponseBody("MD5", MD5_HASH_SIMPLE_MAR); |
|
279 run_test_helper_pt1("mar download with offline mode", |
|
280 AUS_Cr.NS_OK, finish_test); |
|
281 } |