Bug 835803 - Add tests for downloads whose size is zero bytes. r=enn

--HG--
extra : rebase_source : ed691306ee8976dabcb77a3947eec660a7a6e02e
This commit is contained in:
Paolo Amadini 2013-03-05 12:10:20 +01:00
parent 342dd989e7
commit 7c2b4f05be
5 changed files with 228 additions and 56 deletions

View File

@ -601,6 +601,13 @@ DownloadCopySaver.prototype = {
onStartRequest: function DCSE_onStartRequest(aRequest, aContext)
{
backgroundFileSaver.onStartRequest(aRequest, aContext);
// Ensure we report the value of "Content-Length", if available, even
// if the download doesn't generate any progress events later.
if (aRequest instanceof Ci.nsIChannel &&
aRequest.contentLength >= 0) {
aSetProgressBytesFn(0, aRequest.contentLength);
}
},
onStopRequest: function DCSE_onStopRequest(aRequest, aContext,
aStatusCode)

View File

@ -11,6 +11,7 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
FILES = \
empty.txt \
source.txt \
$(NULL)

View File

@ -46,8 +46,13 @@ const FAKE_SERVER_PORT = 4445;
const FAKE_BASE = "http://localhost:" + FAKE_SERVER_PORT;
const TEST_SOURCE_URI = NetUtil.newURI(HTTP_BASE + "/source.txt");
const TEST_EMPTY_URI = NetUtil.newURI(HTTP_BASE + "/empty.txt");
const TEST_FAKE_SOURCE_URI = NetUtil.newURI(FAKE_BASE + "/source.txt");
const TEST_EMPTY_NOPROGRESS_PATH = "/empty-noprogress.txt";
const TEST_EMPTY_NOPROGRESS_URI = NetUtil.newURI(HTTP_BASE +
TEST_EMPTY_NOPROGRESS_PATH);
const TEST_INTERRUPTIBLE_PATH = "/interruptible.txt";
const TEST_INTERRUPTIBLE_URI = NetUtil.newURI(HTTP_BASE +
TEST_INTERRUPTIBLE_PATH);
@ -90,6 +95,20 @@ function getTempFile(aLeafName)
return file;
}
/**
* Waits for pending events to be processed.
*
* @return {Promise}
* @resolves When pending events have been processed.
* @rejects Never.
*/
function promiseExecuteSoon()
{
let deferred = Promise.defer();
do_execute_soon(deferred.resolve);
return deferred.promise;
}
/**
* Creates a new Download object, using TEST_TARGET_FILE_NAME as the target.
* The target is deleted by getTempFile when this function is called.
@ -162,8 +181,15 @@ function startFakeServer()
* This function allows testing events or actions that need to happen in the
* middle of a download.
*
* Calling this function registers a new request handler in the internal HTTP
* server, accessible at the TEST_INTERRUPTIBLE_URI address. The HTTP handler
* Normally, the internal HTTP server returns all the available data as soon as
* a request is received. In order for some requests to be served one part at a
* time, special interruptible handlers are registered on the HTTP server.
*
* Before making a request to one of the addresses served by the interruptible
* handlers, you may call "deferNextResponse" to get a reference to an object
* that allows you to control the next request.
*
* For example, the handler accessible at the TEST_INTERRUPTIBLE_URI address
* returns the TEST_DATA_SHORT text, then waits until the "resolve" method is
* called on the object returned by the function. At this point, the handler
* sends the TEST_DATA_SHORT text again to complete the response.
@ -172,32 +198,85 @@ function startFakeServer()
* response midway. Because of how the network layer is implemented, this does
* not cause the socket to return an error.
*
* The handler is unregistered when the response finishes or is interrupted.
*
* @returns Deferred object used to control the response.
*/
function startInterruptibleResponseHandler()
function deferNextResponse()
{
let deferResponse = Promise.defer();
gHttpServer.registerPathHandler(TEST_INTERRUPTIBLE_PATH,
function (aRequest, aResponse)
{
aResponse.processAsync();
aResponse.setHeader("Content-Type", "text/plain", false);
aResponse.setHeader("Content-Length", "" + (TEST_DATA_SHORT.length * 2),
false);
aResponse.write(TEST_DATA_SHORT);
do_print("Interruptible request will be controlled.");
deferResponse.promise.then(function SIRH_onSuccess() {
aResponse.write(TEST_DATA_SHORT);
aResponse.finish();
gHttpServer.registerPathHandler(TEST_INTERRUPTIBLE_PATH, null);
}, function SIRH_onFailure() {
aResponse.abort();
gHttpServer.registerPathHandler(TEST_INTERRUPTIBLE_PATH, null);
});
// Store an internal reference that should not be used directly by tests.
if (!deferNextResponse._deferred) {
deferNextResponse._deferred = Promise.defer();
}
return deferNextResponse._deferred;
}
/**
* Returns a promise that is resolved when the next interruptible response
* handler has received the request, and has started sending the first part of
* the response. The response might not have been received by the client yet.
*
* @return {Promise}
* @resolves When the next request has been received.
* @rejects Never.
*/
function promiseNextRequestReceived()
{
do_print("Requested notification when interruptible request is received.");
// Store an internal reference that should not be used directly by tests.
promiseNextRequestReceived._deferred = Promise.defer();
return promiseNextRequestReceived._deferred.promise;
}
/**
* Registers an interruptible response handler.
*
* @param aPath
* Path passed to nsIHttpServer.registerPathHandler.
* @param aFirstPartFn
* This function is called when the response is received, with the
* aRequest and aResponse arguments of the server.
* @param aSecondPartFn
* This function is called after the "resolve" method of the object
* returned by deferNextResponse is called. This function is called with
* the aRequest and aResponse arguments of the server.
*/
function registerInterruptibleHandler(aPath, aFirstPartFn, aSecondPartFn)
{
gHttpServer.registerPathHandler(aPath, function (aRequest, aResponse) {
// Get a reference to the controlling object for this request. If the
// deferNextResponse function was not called, interrupt the test.
let deferResponse = deferNextResponse._deferred;
deferNextResponse._deferred = null;
if (deferResponse) {
do_print("Interruptible request started under control.");
} else {
do_print("Interruptible request started without being controlled.");
deferResponse = Promise.defer();
deferResponse.resolve();
}
// Process the first part of the response.
aResponse.processAsync();
aFirstPartFn(aRequest, aResponse);
if (promiseNextRequestReceived._deferred) {
do_print("Notifying that interruptible request has been received.");
promiseNextRequestReceived._deferred.resolve();
promiseNextRequestReceived._deferred = null;
}
// Wait on the deferred object, then finish or abort the request.
deferResponse.promise.then(function RIH_onSuccess() {
aSecondPartFn(aRequest, aResponse);
aResponse.finish();
do_print("Interruptible request finished.");
}, function RIH_onFailure() {
aResponse.abort();
do_print("Interruptible request aborted.");
});
return deferResponse;
});
}
////////////////////////////////////////////////////////////////////////////////
@ -211,4 +290,19 @@ add_task(function test_common_initialize()
gHttpServer = new HttpServer();
gHttpServer.registerDirectory("/", do_get_file("../data"));
gHttpServer.start(HTTP_SERVER_PORT);
registerInterruptibleHandler(TEST_INTERRUPTIBLE_PATH,
function firstPart(aRequest, aResponse) {
aResponse.setHeader("Content-Type", "text/plain", false);
aResponse.setHeader("Content-Length", "" + (TEST_DATA_SHORT.length * 2),
false);
aResponse.write(TEST_DATA_SHORT);
}, function secondPart(aRequest, aResponse) {
aResponse.write(TEST_DATA_SHORT);
});
registerInterruptibleHandler(TEST_EMPTY_NOPROGRESS_PATH,
function firstPart(aRequest, aResponse) {
aResponse.setHeader("Content-Type", "text/plain", false);
}, function secondPart(aRequest, aResponse) { });
});

View File

@ -88,7 +88,7 @@ add_task(function test_download_final_state_notified()
*/
add_task(function test_download_intermediate_progress()
{
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
@ -99,7 +99,7 @@ add_task(function test_download_intermediate_progress()
do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2);
// Continue after the first chunk of data is fully received.
interruptible.resolve();
deferResponse.resolve();
}
};
@ -113,6 +113,71 @@ add_task(function test_download_intermediate_progress()
TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
* Downloads a file with a "Content-Length" of 0 and checks the progress.
*/
add_task(function test_download_empty_progress()
{
let download = yield promiseSimpleDownload(TEST_EMPTY_URI);
yield download.start();
do_check_true(download.stopped);
do_check_true(download.hasProgress);
do_check_eq(download.progress, 100);
do_check_eq(download.currentBytes, 0);
do_check_eq(download.totalBytes, 0);
do_check_eq(download.target.file.fileSize, 0);
});
/**
* Downloads an empty file with no "Content-Length" and checks the progress.
*/
add_task(function test_download_empty_noprogress()
{
let deferResponse = deferNextResponse();
let promiseEmptyRequestReceived = promiseNextRequestReceived();
let download = yield promiseSimpleDownload(TEST_EMPTY_NOPROGRESS_URI);
download.onchange = function () {
if (!download.stopped) {
do_check_false(download.hasProgress);
do_check_eq(download.currentBytes, 0);
do_check_eq(download.totalBytes, 0);
}
};
// Start the download, while waiting for the request to be received.
let promiseAttempt = download.start();
// Wait for the request to be received by the HTTP server, but don't allow the
// request to finish yet. Before checking the download state, wait for the
// events to be processed by the client.
yield promiseEmptyRequestReceived;
yield promiseExecuteSoon();
// Check that this download has no progress report.
do_check_false(download.stopped);
do_check_false(download.hasProgress);
do_check_eq(download.currentBytes, 0);
do_check_eq(download.totalBytes, 0);
// Now allow the response to finish, and wait for the download to complete.
deferResponse.resolve();
yield promiseAttempt;
// Verify the state of the completed download.
do_check_true(download.stopped);
do_check_false(download.hasProgress);
do_check_eq(download.progress, 100);
do_check_eq(download.currentBytes, 0);
do_check_eq(download.totalBytes, 0);
do_check_eq(download.target.file.fileSize, 0);
});
/**
* Calls the "start" method two times before the download is finished.
*/
@ -121,14 +186,14 @@ add_task(function test_download_start_twice()
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
// Ensure that the download cannot complete before start is called twice.
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
// Call the start method two times.
let promiseAttempt1 = download.start();
let promiseAttempt2 = download.start();
// Allow the download to finish.
interruptible.resolve();
deferResponse.resolve();
// Both promises should now be resolved.
yield promiseAttempt1;
@ -150,7 +215,7 @@ add_task(function test_download_cancel_midway()
{
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
try {
// Cancel the download after receiving the first part of the response.
let deferCancel = Promise.defer();
@ -190,7 +255,7 @@ add_task(function test_download_cancel_midway()
do_check_false(ex.becauseTargetFailed);
}
} finally {
interruptible.reject();
deferResponse.resolve();
}
});
@ -200,7 +265,7 @@ add_task(function test_download_cancel_midway()
add_task(function test_download_cancel_immediately()
{
// Ensure that the download cannot complete before cancel is called.
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
try {
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
@ -230,8 +295,14 @@ add_task(function test_download_cancel_immediately()
// Check that the promise returned by the "cancel" method has been resolved.
yield promiseCancel;
} finally {
interruptible.reject();
deferResponse.resolve();
}
// Even if we canceled the download immediately, the HTTP request might have
// been made, and the internal HTTP handler might be waiting to process it.
// Thus, we process any pending events now, to avoid that the request is
// processed during the tests that follow, interfering with them.
yield promiseExecuteSoon();
});
/**
@ -242,7 +313,7 @@ add_task(function test_download_cancel_midway_restart()
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
// The first time, cancel the download midway.
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
try {
let deferCancel = Promise.defer();
download.onchange = function () {
@ -253,13 +324,12 @@ add_task(function test_download_cancel_midway_restart()
download.start();
yield deferCancel.promise;
} finally {
interruptible.reject();
deferResponse.resolve();
}
do_check_true(download.stopped);
// The second time, we'll provide the entire interruptible response.
startInterruptibleResponseHandler().resolve();
download.onchange = null;
let promiseAttempt = download.start();
@ -275,9 +345,6 @@ add_task(function test_download_cancel_midway_restart()
do_check_eq(download.totalBytes, 0);
do_check_eq(download.currentBytes, 0);
// Allow the second request to complete.
interruptible.resolve();
yield promiseAttempt;
do_check_true(download.stopped);
@ -297,7 +364,7 @@ add_task(function test_download_cancel_immediately_restart_immediately()
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
// Ensure that the download cannot complete before cancel is called.
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
let promiseAttempt = download.start();
do_check_false(download.stopped);
@ -318,8 +385,15 @@ add_task(function test_download_cancel_immediately_restart_immediately()
do_check_eq(download.totalBytes, 0);
do_check_eq(download.currentBytes, 0);
// Allow the second request to complete.
startInterruptibleResponseHandler().resolve();
// Even if we canceled the download immediately, the HTTP request might have
// been made, and the internal HTTP handler might be waiting to process it.
// Thus, we process any pending events now, to avoid that the request is
// processed during the tests that follow, interfering with them.
yield promiseExecuteSoon();
// Ensure the next request is now allowed to complete, regardless of whether
// the canceled request was received by the server or not.
deferResponse.resolve();
try {
yield promiseAttempt;
@ -336,7 +410,8 @@ add_task(function test_download_cancel_immediately_restart_immediately()
do_check_false(download.canceled);
do_check_true(download.error === null);
do_check_true(download.target.file.exists());
yield promiseVerifyContents(download.target.file,
TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**
@ -347,7 +422,7 @@ add_task(function test_download_cancel_midway_restart_immediately()
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
// The first time, cancel the download midway.
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
let deferMidway = Promise.defer();
download.onchange = function () {
@ -375,11 +450,9 @@ add_task(function test_download_cancel_midway_restart_immediately()
do_check_eq(download.totalBytes, 0);
do_check_eq(download.currentBytes, 0);
interruptible.reject();
// Allow the second request to complete.
startInterruptibleResponseHandler().resolve();
deferResponse.resolve();
// The second request is allowed to complete.
try {
yield promiseAttempt;
do_throw("The download should have been canceled.");
@ -395,8 +468,6 @@ add_task(function test_download_cancel_midway_restart_immediately()
do_check_false(download.canceled);
do_check_true(download.error === null);
do_check_true(download.target.file.exists());
yield promiseVerifyContents(download.target.file,
TEST_DATA_SHORT + TEST_DATA_SHORT);
});
@ -430,8 +501,7 @@ add_task(function test_download_cancel_twice()
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
// Ensure that the download cannot complete before cancel is called.
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
try {
let promiseAttempt = download.start();
do_check_false(download.stopped);
@ -459,7 +529,7 @@ add_task(function test_download_cancel_twice()
do_check_false(download.target.file.exists());
} finally {
interruptible.reject();
deferResponse.resolve();
}
});
@ -471,7 +541,7 @@ add_task(function test_download_whenSucceeded()
let download = yield promiseSimpleDownload(TEST_INTERRUPTIBLE_URI);
// Ensure that the download cannot complete before cancel is called.
let interruptible = startInterruptibleResponseHandler();
let deferResponse = deferNextResponse();
// Get a reference before the first download attempt.
let promiseSucceeded = download.whenSucceeded();
@ -480,10 +550,9 @@ add_task(function test_download_whenSucceeded()
download.start();
yield download.cancel();
interruptible.reject();
deferResponse.resolve();
// Allow the second request to complete.
startInterruptibleResponseHandler().resolve();
// The second request is allowed to complete.
download.start();
// Wait for the download to finish by waiting on the whenSucceeded promise.
@ -494,7 +563,8 @@ add_task(function test_download_whenSucceeded()
do_check_false(download.canceled);
do_check_true(download.error === null);
do_check_true(download.target.file.exists());
yield promiseVerifyContents(download.target.file,
TEST_DATA_SHORT + TEST_DATA_SHORT);
});
/**