|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* Any copyright is dedicated to the Public Domain. |
|
4 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
5 |
|
6 /** |
|
7 * This file tests components that implement nsIBackgroundFileSaver. |
|
8 */ |
|
9 |
|
10 //////////////////////////////////////////////////////////////////////////////// |
|
11 //// Globals |
|
12 |
|
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
14 |
|
15 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
|
16 "resource://gre/modules/FileUtils.jsm"); |
|
17 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", |
|
18 "resource://gre/modules/NetUtil.jsm"); |
|
19 XPCOMUtils.defineLazyModuleGetter(this, "Promise", |
|
20 "resource://gre/modules/Promise.jsm"); |
|
21 XPCOMUtils.defineLazyModuleGetter(this, "Task", |
|
22 "resource://gre/modules/Task.jsm"); |
|
23 |
|
24 const BackgroundFileSaverOutputStream = Components.Constructor( |
|
25 "@mozilla.org/network/background-file-saver;1?mode=outputstream", |
|
26 "nsIBackgroundFileSaver"); |
|
27 |
|
28 const BackgroundFileSaverStreamListener = Components.Constructor( |
|
29 "@mozilla.org/network/background-file-saver;1?mode=streamlistener", |
|
30 "nsIBackgroundFileSaver"); |
|
31 |
|
32 const StringInputStream = Components.Constructor( |
|
33 "@mozilla.org/io/string-input-stream;1", |
|
34 "nsIStringInputStream", |
|
35 "setData"); |
|
36 |
|
37 const REQUEST_SUSPEND_AT = 1024 * 1024 * 4; |
|
38 const TEST_DATA_SHORT = "This test string is written to the file."; |
|
39 const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt"; |
|
40 const TEST_FILE_NAME_2 = "test-backgroundfilesaver-2.txt"; |
|
41 const TEST_FILE_NAME_3 = "test-backgroundfilesaver-3.txt"; |
|
42 |
|
43 // A map of test data length to the expected SHA-256 hashes |
|
44 const EXPECTED_HASHES = { |
|
45 // No data |
|
46 0 : "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", |
|
47 // TEST_DATA_SHORT |
|
48 40 : "f37176b690e8744ee990a206c086cba54d1502aa2456c3b0c84ef6345d72a192", |
|
49 // TEST_DATA_SHORT + TEST_DATA_SHORT |
|
50 80 : "780c0e91f50bb7ec922cc11e16859e6d5df283c0d9470f61772e3d79f41eeb58", |
|
51 // TEST_DATA_LONG |
|
52 8388608 : "e3611a47714c42bdf326acfb2eb6ed9fa4cca65cb7d7be55217770a5bf5e7ff0", |
|
53 // TEST_DATA_LONG + TEST_DATA_LONG |
|
54 16777216 : "03a0db69a30140f307587ee746a539247c181bafd85b85c8516a3533c7d9ea1d" |
|
55 }; |
|
56 |
|
57 const gTextDecoder = new TextDecoder(); |
|
58 |
|
59 // Generate a long string of data in a moderately fast way. |
|
60 const TEST_256_CHARS = new Array(257).join("-"); |
|
61 const DESIRED_LENGTH = REQUEST_SUSPEND_AT * 2; |
|
62 const TEST_DATA_LONG = new Array(1 + DESIRED_LENGTH / 256).join(TEST_256_CHARS); |
|
63 do_check_eq(TEST_DATA_LONG.length, DESIRED_LENGTH); |
|
64 |
|
65 /** |
|
66 * Returns a reference to a temporary file. If the file is then created, it |
|
67 * will be removed when tests in this file finish. |
|
68 */ |
|
69 function getTempFile(aLeafName) { |
|
70 let file = FileUtils.getFile("TmpD", [aLeafName]); |
|
71 do_register_cleanup(function GTF_cleanup() { |
|
72 if (file.exists()) { |
|
73 file.remove(false); |
|
74 } |
|
75 }); |
|
76 return file; |
|
77 } |
|
78 |
|
79 /** |
|
80 * Helper function for converting a binary blob to its hex equivalent. |
|
81 * |
|
82 * @param str |
|
83 * String possibly containing non-printable chars. |
|
84 * @return A hex-encoded string. |
|
85 */ |
|
86 function toHex(str) { |
|
87 var hex = ''; |
|
88 for (var i = 0; i < str.length; i++) { |
|
89 hex += ('0' + str.charCodeAt(i).toString(16)).slice(-2); |
|
90 } |
|
91 return hex; |
|
92 } |
|
93 |
|
94 /** |
|
95 * Ensures that the given file contents are equal to the given string. |
|
96 * |
|
97 * @param aFile |
|
98 * nsIFile whose contents should be verified. |
|
99 * @param aExpectedContents |
|
100 * String containing the octets that are expected in the file. |
|
101 * |
|
102 * @return {Promise} |
|
103 * @resolves When the operation completes. |
|
104 * @rejects Never. |
|
105 */ |
|
106 function promiseVerifyContents(aFile, aExpectedContents) { |
|
107 let deferred = Promise.defer(); |
|
108 NetUtil.asyncFetch(aFile, function(aInputStream, aStatus) { |
|
109 do_check_true(Components.isSuccessCode(aStatus)); |
|
110 let contents = NetUtil.readInputStreamToString(aInputStream, |
|
111 aInputStream.available()); |
|
112 if (contents.length <= TEST_DATA_SHORT.length * 2) { |
|
113 do_check_eq(contents, aExpectedContents); |
|
114 } else { |
|
115 // Do not print the entire content string to the test log. |
|
116 do_check_eq(contents.length, aExpectedContents.length); |
|
117 do_check_true(contents == aExpectedContents); |
|
118 } |
|
119 deferred.resolve(); |
|
120 }); |
|
121 return deferred.promise; |
|
122 } |
|
123 |
|
124 /** |
|
125 * Waits for the given saver object to complete. |
|
126 * |
|
127 * @param aSaver |
|
128 * The saver, with the output stream or a stream listener implementation. |
|
129 * @param aOnTargetChangeFn |
|
130 * Optional callback invoked with the target file name when it changes. |
|
131 * |
|
132 * @return {Promise} |
|
133 * @resolves When onSaveComplete is called with a success code. |
|
134 * @rejects With an exception, if onSaveComplete is called with a failure code. |
|
135 */ |
|
136 function promiseSaverComplete(aSaver, aOnTargetChangeFn) { |
|
137 let deferred = Promise.defer(); |
|
138 aSaver.observer = { |
|
139 onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget) |
|
140 { |
|
141 if (aOnTargetChangeFn) { |
|
142 aOnTargetChangeFn(aTarget); |
|
143 } |
|
144 }, |
|
145 onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus) |
|
146 { |
|
147 if (Components.isSuccessCode(aStatus)) { |
|
148 deferred.resolve(); |
|
149 } else { |
|
150 deferred.reject(new Components.Exception("Saver failed.", aStatus)); |
|
151 } |
|
152 }, |
|
153 }; |
|
154 return deferred.promise; |
|
155 } |
|
156 |
|
157 /** |
|
158 * Feeds a string to a BackgroundFileSaverOutputStream. |
|
159 * |
|
160 * @param aSourceString |
|
161 * The source data to copy. |
|
162 * @param aSaverOutputStream |
|
163 * The BackgroundFileSaverOutputStream to feed. |
|
164 * @param aCloseWhenDone |
|
165 * If true, the output stream will be closed when the copy finishes. |
|
166 * |
|
167 * @return {Promise} |
|
168 * @resolves When the copy completes with a success code. |
|
169 * @rejects With an exception, if the copy fails. |
|
170 */ |
|
171 function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) { |
|
172 let deferred = Promise.defer(); |
|
173 let inputStream = new StringInputStream(aSourceString, aSourceString.length); |
|
174 let copier = Cc["@mozilla.org/network/async-stream-copier;1"] |
|
175 .createInstance(Ci.nsIAsyncStreamCopier); |
|
176 copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true, |
|
177 aCloseWhenDone); |
|
178 copier.asyncCopy({ |
|
179 onStartRequest: function () { }, |
|
180 onStopRequest: function (aRequest, aContext, aStatusCode) |
|
181 { |
|
182 if (Components.isSuccessCode(aStatusCode)) { |
|
183 deferred.resolve(); |
|
184 } else { |
|
185 deferred.reject(new Components.Exception(aResult)); |
|
186 } |
|
187 }, |
|
188 }, null); |
|
189 return deferred.promise; |
|
190 } |
|
191 |
|
192 /** |
|
193 * Feeds a string to a BackgroundFileSaverStreamListener. |
|
194 * |
|
195 * @param aSourceString |
|
196 * The source data to copy. |
|
197 * @param aSaverStreamListener |
|
198 * The BackgroundFileSaverStreamListener to feed. |
|
199 * @param aCloseWhenDone |
|
200 * If true, the output stream will be closed when the copy finishes. |
|
201 * |
|
202 * @return {Promise} |
|
203 * @resolves When the operation completes with a success code. |
|
204 * @rejects With an exception, if the operation fails. |
|
205 */ |
|
206 function promisePumpToSaver(aSourceString, aSaverStreamListener, |
|
207 aCloseWhenDone) { |
|
208 let deferred = Promise.defer(); |
|
209 aSaverStreamListener.QueryInterface(Ci.nsIStreamListener); |
|
210 let inputStream = new StringInputStream(aSourceString, aSourceString.length); |
|
211 let pump = Cc["@mozilla.org/network/input-stream-pump;1"] |
|
212 .createInstance(Ci.nsIInputStreamPump); |
|
213 pump.init(inputStream, -1, -1, 0, 0, true); |
|
214 pump.asyncRead({ |
|
215 onStartRequest: function PPTS_onStartRequest(aRequest, aContext) |
|
216 { |
|
217 aSaverStreamListener.onStartRequest(aRequest, aContext); |
|
218 }, |
|
219 onStopRequest: function PPTS_onStopRequest(aRequest, aContext, aStatusCode) |
|
220 { |
|
221 aSaverStreamListener.onStopRequest(aRequest, aContext, aStatusCode); |
|
222 if (Components.isSuccessCode(aStatusCode)) { |
|
223 deferred.resolve(); |
|
224 } else { |
|
225 deferred.reject(new Components.Exception(aResult)); |
|
226 } |
|
227 }, |
|
228 onDataAvailable: function PPTS_onDataAvailable(aRequest, aContext, |
|
229 aInputStream, aOffset, |
|
230 aCount) |
|
231 { |
|
232 aSaverStreamListener.onDataAvailable(aRequest, aContext, aInputStream, |
|
233 aOffset, aCount); |
|
234 }, |
|
235 }, null); |
|
236 return deferred.promise; |
|
237 } |
|
238 |
|
239 let gStillRunning = true; |
|
240 |
|
241 //////////////////////////////////////////////////////////////////////////////// |
|
242 //// Tests |
|
243 |
|
244 function run_test() |
|
245 { |
|
246 run_next_test(); |
|
247 } |
|
248 |
|
249 add_task(function test_setup() |
|
250 { |
|
251 // Wait 10 minutes, that is half of the external xpcshell timeout. |
|
252 do_timeout(10 * 60 * 1000, function() { |
|
253 if (gStillRunning) { |
|
254 do_throw("Test timed out."); |
|
255 } |
|
256 }) |
|
257 }); |
|
258 |
|
259 add_task(function test_normal() |
|
260 { |
|
261 // This test demonstrates the most basic use case. |
|
262 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
263 |
|
264 // Create the object implementing the output stream. |
|
265 let saver = new BackgroundFileSaverOutputStream(); |
|
266 |
|
267 // Set up callbacks for completion and target file name change. |
|
268 let receivedOnTargetChange = false; |
|
269 function onTargetChange(aTarget) { |
|
270 do_check_true(destFile.equals(aTarget)); |
|
271 receivedOnTargetChange = true; |
|
272 } |
|
273 let completionPromise = promiseSaverComplete(saver, onTargetChange); |
|
274 |
|
275 // Set the target file. |
|
276 saver.setTarget(destFile, false); |
|
277 |
|
278 // Write some data and close the output stream. |
|
279 yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
|
280 |
|
281 // Indicate that we are ready to finish, and wait for a successful callback. |
|
282 saver.finish(Cr.NS_OK); |
|
283 yield completionPromise; |
|
284 |
|
285 // Only after we receive the completion notification, we can also be sure that |
|
286 // we've received the target file name change notification before it. |
|
287 do_check_true(receivedOnTargetChange); |
|
288 |
|
289 // Clean up. |
|
290 destFile.remove(false); |
|
291 }); |
|
292 |
|
293 add_task(function test_combinations() |
|
294 { |
|
295 let initialFile = getTempFile(TEST_FILE_NAME_1); |
|
296 let renamedFile = getTempFile(TEST_FILE_NAME_2); |
|
297 |
|
298 // Keep track of the current file. |
|
299 let currentFile = null; |
|
300 function onTargetChange(aTarget) { |
|
301 currentFile = null; |
|
302 do_print("Target file changed to: " + aTarget.leafName); |
|
303 currentFile = aTarget; |
|
304 } |
|
305 |
|
306 // Tests various combinations of events and behaviors for both the stream |
|
307 // listener and the output stream implementations. |
|
308 for (let testFlags = 0; testFlags < 32; testFlags++) { |
|
309 let keepPartialOnFailure = !!(testFlags & 1); |
|
310 let renameAtSomePoint = !!(testFlags & 2); |
|
311 let cancelAtSomePoint = !!(testFlags & 4); |
|
312 let useStreamListener = !!(testFlags & 8); |
|
313 let useLongData = !!(testFlags & 16); |
|
314 |
|
315 let startTime = Date.now(); |
|
316 do_print("Starting keepPartialOnFailure = " + keepPartialOnFailure + |
|
317 ", renameAtSomePoint = " + renameAtSomePoint + |
|
318 ", cancelAtSomePoint = " + cancelAtSomePoint + |
|
319 ", useStreamListener = " + useStreamListener + |
|
320 ", useLongData = " + useLongData); |
|
321 |
|
322 // Create the object and register the observers. |
|
323 currentFile = null; |
|
324 let saver = useStreamListener |
|
325 ? new BackgroundFileSaverStreamListener() |
|
326 : new BackgroundFileSaverOutputStream(); |
|
327 saver.enableSha256(); |
|
328 let completionPromise = promiseSaverComplete(saver, onTargetChange); |
|
329 |
|
330 // Start feeding the first chunk of data to the saver. In case we are using |
|
331 // the stream listener, we only write one chunk. |
|
332 let testData = useLongData ? TEST_DATA_LONG : TEST_DATA_SHORT; |
|
333 let feedPromise = useStreamListener |
|
334 ? promisePumpToSaver(testData + testData, saver) |
|
335 : promiseCopyToSaver(testData, saver, false); |
|
336 |
|
337 // Set a target output file. |
|
338 saver.setTarget(initialFile, keepPartialOnFailure); |
|
339 |
|
340 // Wait for the first chunk of data to be copied. |
|
341 yield feedPromise; |
|
342 |
|
343 if (renameAtSomePoint) { |
|
344 saver.setTarget(renamedFile, keepPartialOnFailure); |
|
345 } |
|
346 |
|
347 if (cancelAtSomePoint) { |
|
348 saver.finish(Cr.NS_ERROR_FAILURE); |
|
349 } |
|
350 |
|
351 // Feed the second chunk of data to the saver. |
|
352 if (!useStreamListener) { |
|
353 yield promiseCopyToSaver(testData, saver, true); |
|
354 } |
|
355 |
|
356 // Wait for completion, and ensure we succeeded or failed as expected. |
|
357 if (!cancelAtSomePoint) { |
|
358 saver.finish(Cr.NS_OK); |
|
359 } |
|
360 try { |
|
361 yield completionPromise; |
|
362 if (cancelAtSomePoint) { |
|
363 do_throw("Failure expected."); |
|
364 } |
|
365 } catch (ex if cancelAtSomePoint && ex.result == Cr.NS_ERROR_FAILURE) { } |
|
366 |
|
367 if (!cancelAtSomePoint) { |
|
368 // In this case, the file must exist. |
|
369 do_check_true(currentFile.exists()); |
|
370 let expectedContents = testData + testData; |
|
371 yield promiseVerifyContents(currentFile, expectedContents); |
|
372 do_check_eq(EXPECTED_HASHES[expectedContents.length], |
|
373 toHex(saver.sha256Hash)); |
|
374 currentFile.remove(false); |
|
375 |
|
376 // If the target was really renamed, the old file should not exist. |
|
377 if (renamedFile.equals(currentFile)) { |
|
378 do_check_false(initialFile.exists()); |
|
379 } |
|
380 } else if (!keepPartialOnFailure) { |
|
381 // In this case, the file must not exist. |
|
382 do_check_false(initialFile.exists()); |
|
383 do_check_false(renamedFile.exists()); |
|
384 } else { |
|
385 // In this case, the file may or may not exist, because canceling can |
|
386 // interrupt the asynchronous operation at any point, even before the file |
|
387 // has been created for the first time. |
|
388 if (initialFile.exists()) { |
|
389 initialFile.remove(false); |
|
390 } |
|
391 if (renamedFile.exists()) { |
|
392 renamedFile.remove(false); |
|
393 } |
|
394 } |
|
395 |
|
396 do_print("Test case completed in " + (Date.now() - startTime) + " ms."); |
|
397 } |
|
398 }); |
|
399 |
|
400 add_task(function test_setTarget_after_close_stream() |
|
401 { |
|
402 // This test checks the case where we close the output stream before we call |
|
403 // the setTarget method. All the data should be buffered and written anyway. |
|
404 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
405 |
|
406 // Test the case where the file does not already exists first, then the case |
|
407 // where the file already exists. |
|
408 for (let i = 0; i < 2; i++) { |
|
409 let saver = new BackgroundFileSaverOutputStream(); |
|
410 saver.enableSha256(); |
|
411 let completionPromise = promiseSaverComplete(saver); |
|
412 |
|
413 // Copy some data to the output stream of the file saver. This data must |
|
414 // be shorter than the internal component's pipe buffer for the test to |
|
415 // succeed, because otherwise the test would block waiting for the write to |
|
416 // complete. |
|
417 yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
|
418 |
|
419 // Set the target file and wait for the output to finish. |
|
420 saver.setTarget(destFile, false); |
|
421 saver.finish(Cr.NS_OK); |
|
422 yield completionPromise; |
|
423 |
|
424 // Verify results. |
|
425 yield promiseVerifyContents(destFile, TEST_DATA_SHORT); |
|
426 do_check_eq(EXPECTED_HASHES[TEST_DATA_SHORT.length], |
|
427 toHex(saver.sha256Hash)); |
|
428 } |
|
429 |
|
430 // Clean up. |
|
431 destFile.remove(false); |
|
432 }); |
|
433 |
|
434 add_task(function test_setTarget_fast() |
|
435 { |
|
436 // This test checks a fast rename of the target file. |
|
437 let destFile1 = getTempFile(TEST_FILE_NAME_1); |
|
438 let destFile2 = getTempFile(TEST_FILE_NAME_2); |
|
439 let saver = new BackgroundFileSaverOutputStream(); |
|
440 let completionPromise = promiseSaverComplete(saver); |
|
441 |
|
442 // Set the initial name after the stream is closed, then rename immediately. |
|
443 yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
|
444 saver.setTarget(destFile1, false); |
|
445 saver.setTarget(destFile2, false); |
|
446 |
|
447 // Wait for all the operations to complete. |
|
448 saver.finish(Cr.NS_OK); |
|
449 yield completionPromise; |
|
450 |
|
451 // Verify results and clean up. |
|
452 do_check_false(destFile1.exists()); |
|
453 yield promiseVerifyContents(destFile2, TEST_DATA_SHORT); |
|
454 destFile2.remove(false); |
|
455 }); |
|
456 |
|
457 add_task(function test_setTarget_multiple() |
|
458 { |
|
459 // This test checks multiple renames of the target file. |
|
460 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
461 let saver = new BackgroundFileSaverOutputStream(); |
|
462 let completionPromise = promiseSaverComplete(saver); |
|
463 |
|
464 // Rename both before and after the stream is closed. |
|
465 saver.setTarget(getTempFile(TEST_FILE_NAME_2), false); |
|
466 saver.setTarget(getTempFile(TEST_FILE_NAME_3), false); |
|
467 yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
|
468 saver.setTarget(getTempFile(TEST_FILE_NAME_2), false); |
|
469 saver.setTarget(destFile, false); |
|
470 |
|
471 // Wait for all the operations to complete. |
|
472 saver.finish(Cr.NS_OK); |
|
473 yield completionPromise; |
|
474 |
|
475 // Verify results and clean up. |
|
476 do_check_false(getTempFile(TEST_FILE_NAME_2).exists()); |
|
477 do_check_false(getTempFile(TEST_FILE_NAME_3).exists()); |
|
478 yield promiseVerifyContents(destFile, TEST_DATA_SHORT); |
|
479 destFile.remove(false); |
|
480 }); |
|
481 |
|
482 add_task(function test_enableAppend() |
|
483 { |
|
484 // This test checks append mode with hashing disabled. |
|
485 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
486 |
|
487 // Test the case where the file does not already exists first, then the case |
|
488 // where the file already exists. |
|
489 for (let i = 0; i < 2; i++) { |
|
490 let saver = new BackgroundFileSaverOutputStream(); |
|
491 saver.enableAppend(); |
|
492 let completionPromise = promiseSaverComplete(saver); |
|
493 |
|
494 saver.setTarget(destFile, false); |
|
495 yield promiseCopyToSaver(TEST_DATA_LONG, saver, true); |
|
496 |
|
497 saver.finish(Cr.NS_OK); |
|
498 yield completionPromise; |
|
499 |
|
500 // Verify results. |
|
501 let expectedContents = (i == 0 ? TEST_DATA_LONG |
|
502 : TEST_DATA_LONG + TEST_DATA_LONG); |
|
503 yield promiseVerifyContents(destFile, expectedContents); |
|
504 } |
|
505 |
|
506 // Clean up. |
|
507 destFile.remove(false); |
|
508 }); |
|
509 |
|
510 add_task(function test_enableAppend_setTarget_fast() |
|
511 { |
|
512 // This test checks a fast rename of the target file in append mode. |
|
513 let destFile1 = getTempFile(TEST_FILE_NAME_1); |
|
514 let destFile2 = getTempFile(TEST_FILE_NAME_2); |
|
515 |
|
516 // Test the case where the file does not already exists first, then the case |
|
517 // where the file already exists. |
|
518 for (let i = 0; i < 2; i++) { |
|
519 let saver = new BackgroundFileSaverOutputStream(); |
|
520 saver.enableAppend(); |
|
521 let completionPromise = promiseSaverComplete(saver); |
|
522 |
|
523 yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
|
524 |
|
525 // The first time, we start appending to the first file and rename to the |
|
526 // second file. The second time, we start appending to the second file, |
|
527 // that was created the first time, and rename back to the first file. |
|
528 let firstFile = (i == 0) ? destFile1 : destFile2; |
|
529 let secondFile = (i == 0) ? destFile2 : destFile1; |
|
530 saver.setTarget(firstFile, false); |
|
531 saver.setTarget(secondFile, false); |
|
532 |
|
533 saver.finish(Cr.NS_OK); |
|
534 yield completionPromise; |
|
535 |
|
536 // Verify results. |
|
537 do_check_false(firstFile.exists()); |
|
538 let expectedContents = (i == 0 ? TEST_DATA_SHORT |
|
539 : TEST_DATA_SHORT + TEST_DATA_SHORT); |
|
540 yield promiseVerifyContents(secondFile, expectedContents); |
|
541 } |
|
542 |
|
543 // Clean up. |
|
544 destFile1.remove(false); |
|
545 }); |
|
546 |
|
547 add_task(function test_enableAppend_hash() |
|
548 { |
|
549 // This test checks append mode, also verifying that the computed hash |
|
550 // includes the contents of the existing data. |
|
551 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
552 |
|
553 // Test the case where the file does not already exists first, then the case |
|
554 // where the file already exists. |
|
555 for (let i = 0; i < 2; i++) { |
|
556 let saver = new BackgroundFileSaverOutputStream(); |
|
557 saver.enableAppend(); |
|
558 saver.enableSha256(); |
|
559 let completionPromise = promiseSaverComplete(saver); |
|
560 |
|
561 saver.setTarget(destFile, false); |
|
562 yield promiseCopyToSaver(TEST_DATA_LONG, saver, true); |
|
563 |
|
564 saver.finish(Cr.NS_OK); |
|
565 yield completionPromise; |
|
566 |
|
567 // Verify results. |
|
568 let expectedContents = (i == 0 ? TEST_DATA_LONG |
|
569 : TEST_DATA_LONG + TEST_DATA_LONG); |
|
570 yield promiseVerifyContents(destFile, expectedContents); |
|
571 do_check_eq(EXPECTED_HASHES[expectedContents.length], |
|
572 toHex(saver.sha256Hash)); |
|
573 } |
|
574 |
|
575 // Clean up. |
|
576 destFile.remove(false); |
|
577 }); |
|
578 |
|
579 add_task(function test_finish_only() |
|
580 { |
|
581 // This test checks creating the object and doing nothing. |
|
582 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
583 let saver = new BackgroundFileSaverOutputStream(); |
|
584 function onTargetChange(aTarget) { |
|
585 do_throw("Should not receive the onTargetChange notification."); |
|
586 } |
|
587 let completionPromise = promiseSaverComplete(saver, onTargetChange); |
|
588 saver.finish(Cr.NS_OK); |
|
589 yield completionPromise; |
|
590 }); |
|
591 |
|
592 add_task(function test_empty() |
|
593 { |
|
594 // This test checks we still create an empty file when no data is fed. |
|
595 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
596 |
|
597 let saver = new BackgroundFileSaverOutputStream(); |
|
598 let completionPromise = promiseSaverComplete(saver); |
|
599 |
|
600 saver.setTarget(destFile, false); |
|
601 yield promiseCopyToSaver("", saver, true); |
|
602 |
|
603 saver.finish(Cr.NS_OK); |
|
604 yield completionPromise; |
|
605 |
|
606 // Verify results. |
|
607 do_check_true(destFile.exists()); |
|
608 do_check_eq(destFile.fileSize, 0); |
|
609 |
|
610 // Clean up. |
|
611 destFile.remove(false); |
|
612 }); |
|
613 |
|
614 add_task(function test_empty_hash() |
|
615 { |
|
616 // This test checks the hash of an empty file, both in normal and append mode. |
|
617 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
618 |
|
619 // Test normal mode first, then append mode. |
|
620 for (let i = 0; i < 2; i++) { |
|
621 let saver = new BackgroundFileSaverOutputStream(); |
|
622 if (i == 1) { |
|
623 saver.enableAppend(); |
|
624 } |
|
625 saver.enableSha256(); |
|
626 let completionPromise = promiseSaverComplete(saver); |
|
627 |
|
628 saver.setTarget(destFile, false); |
|
629 yield promiseCopyToSaver("", saver, true); |
|
630 |
|
631 saver.finish(Cr.NS_OK); |
|
632 yield completionPromise; |
|
633 |
|
634 // Verify results. |
|
635 do_check_eq(destFile.fileSize, 0); |
|
636 do_check_eq(EXPECTED_HASHES[0], toHex(saver.sha256Hash)); |
|
637 } |
|
638 |
|
639 // Clean up. |
|
640 destFile.remove(false); |
|
641 }); |
|
642 |
|
643 add_task(function test_invalid_hash() |
|
644 { |
|
645 let saver = new BackgroundFileSaverStreamListener(); |
|
646 let completionPromise = promiseSaverComplete(saver); |
|
647 // We shouldn't be able to get the hash if hashing hasn't been enabled |
|
648 try { |
|
649 let hash = saver.sha256Hash; |
|
650 do_throw("Shouldn't be able to get hash if hashing not enabled"); |
|
651 } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
|
652 // Enable hashing, but don't feed any data to saver |
|
653 saver.enableSha256(); |
|
654 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
655 saver.setTarget(destFile, false); |
|
656 // We don't wait on promiseSaverComplete, so the hash getter can run before |
|
657 // or after onSaveComplete is called. However, the expected behavior is the |
|
658 // same in both cases since the hash is only valid when the save completes |
|
659 // successfully. |
|
660 saver.finish(Cr.NS_ERROR_FAILURE); |
|
661 try { |
|
662 let hash = saver.sha256Hash; |
|
663 do_throw("Shouldn't be able to get hash if save did not succeed"); |
|
664 } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
|
665 // Wait for completion so that the worker thread finishes dealing with the |
|
666 // target file. We expect it to fail. |
|
667 try { |
|
668 yield completionPromise; |
|
669 do_throw("completionPromise should throw"); |
|
670 } catch (ex if ex.result == Cr.NS_ERROR_FAILURE) { } |
|
671 }); |
|
672 |
|
673 add_task(function test_signature() |
|
674 { |
|
675 // Check that we get a signature if the saver is finished. |
|
676 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
677 |
|
678 let saver = new BackgroundFileSaverOutputStream(); |
|
679 let completionPromise = promiseSaverComplete(saver); |
|
680 |
|
681 try { |
|
682 let signatureInfo = saver.signatureInfo; |
|
683 do_throw("Can't get signature if saver is not complete"); |
|
684 } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
|
685 |
|
686 saver.enableSignatureInfo(); |
|
687 saver.setTarget(destFile, false); |
|
688 yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
|
689 |
|
690 saver.finish(Cr.NS_OK); |
|
691 yield completionPromise; |
|
692 yield promiseVerifyContents(destFile, TEST_DATA_SHORT); |
|
693 |
|
694 // signatureInfo is an empty nsIArray |
|
695 do_check_eq(0, saver.signatureInfo.length); |
|
696 |
|
697 // Clean up. |
|
698 destFile.remove(false); |
|
699 }); |
|
700 |
|
701 add_task(function test_signature_not_enabled() |
|
702 { |
|
703 // Check that we get a signature if the saver is finished on Windows. |
|
704 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
705 |
|
706 let saver = new BackgroundFileSaverOutputStream(); |
|
707 let completionPromise = promiseSaverComplete(saver); |
|
708 saver.setTarget(destFile, false); |
|
709 yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
|
710 |
|
711 saver.finish(Cr.NS_OK); |
|
712 yield completionPromise; |
|
713 try { |
|
714 let signatureInfo = saver.signatureInfo; |
|
715 do_throw("Can't get signature if not enabled"); |
|
716 } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
|
717 |
|
718 // Clean up. |
|
719 destFile.remove(false); |
|
720 }); |
|
721 |
|
722 add_task(function test_teardown() |
|
723 { |
|
724 gStillRunning = false; |
|
725 }); |