/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Tests for correct behavior of asynchronous responses.
*/
ChromeUtils.defineLazyGetter(
this,
"PREPATH",
function () {
return "http://localhost:" + srv.identity.primaryPort;
});
var srv;
function run_test() {
srv = createServer();
for (
var path in handlers) {
srv.registerPathHandler(path, handlers[path]);
}
srv.start(-1);
runHttpTests(tests, testComplete(srv));
}
/** *************
* BEGIN TESTS *
***************/
ChromeUtils.defineLazyGetter(
this,
"tests",
function () {
return [
new Test(PREPATH +
"/handleSync",
null, start_handleSync,
null),
new Test(
PREPATH +
"/handleAsync1",
null,
start_handleAsync1,
stop_handleAsync1
),
new Test(
PREPATH +
"/handleAsync2",
init_handleAsync2,
start_handleAsync2,
stop_handleAsync2
),
new Test(
PREPATH +
"/handleAsyncOrdering",
null,
null,
stop_handleAsyncOrdering
),
];
});
var handlers = {};
function handleSync(request, response) {
response.setStatusLine(request.httpVersion, 500,
"handleSync fail");
try {
response.finish();
do_throw(
"finish called on sync response");
}
catch (e) {
isException(e, Cr.NS_ERROR_UNEXPECTED);
}
response.setStatusLine(request.httpVersion, 200,
"handleSync pass");
}
handlers[
"/handleSync"] = handleSync;
function start_handleSync(ch) {
Assert.equal(ch.responseStatus, 200);
Assert.equal(ch.responseStatusText,
"handleSync pass");
}
function handleAsync1(request, response) {
response.setStatusLine(request.httpVersion, 500,
"Old status line!");
response.setHeader(
"X-Foo",
"old value",
false);
response.processAsync();
response.setStatusLine(request.httpVersion, 200,
"New status line!");
response.setHeader(
"X-Foo",
"new value",
false);
response.finish();
try {
response.setStatusLine(request.httpVersion, 500,
"Too late!");
do_throw(
"late setStatusLine didn't throw");
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
try {
response.setHeader(
"X-Foo",
"late value",
false);
do_throw(
"late setHeader didn't throw");
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
try {
response.bodyOutputStream;
do_throw(
"late bodyOutputStream get didn't throw");
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
try {
response.write(
"fugly");
do_throw(
"late write() didn't throw");
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
}
handlers[
"/handleAsync1"] = handleAsync1;
function start_handleAsync1(ch) {
Assert.equal(ch.responseStatus, 200);
Assert.equal(ch.responseStatusText,
"New status line!");
Assert.equal(ch.getResponseHeader(
"X-Foo"),
"new value");
}
function stop_handleAsync1(ch, status, data) {
Assert.equal(data.length, 0);
}
const startToHeaderDelay = 500;
const startToFinishedDelay = 750;
function handleAsync2(request, response) {
response.processAsync();
response.setStatusLine(request.httpVersion, 200,
"Status line");
response.setHeader(
"X-Custom-Header",
"value",
false);
callLater(startToHeaderDelay,
function () {
var preBody =
"BO";
response.bodyOutputStream.write(preBody, preBody.length);
try {
response.setStatusLine(request.httpVersion, 500,
"after body write");
do_throw(
"setStatusLine succeeded");
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
try {
response.setHeader(
"X-Custom-Header",
"new 1",
false);
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
callLater(startToFinishedDelay - startToHeaderDelay,
function () {
var postBody =
"DY";
response.bodyOutputStream.write(postBody, postBody.length);
response.finish();
response.finish();
// idempotency
try {
response.setStatusLine(request.httpVersion, 500,
"after finish");
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
try {
response.setHeader(
"X-Custom-Header",
"new 2",
false);
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
try {
response.write(
"EVIL");
}
catch (e) {
isException(e, Cr.NS_ERROR_NOT_AVAILABLE);
}
});
});
}
handlers[
"/handleAsync2"] = handleAsync2;
var startTime_handleAsync2;
function init_handleAsync2() {
var now = (startTime_handleAsync2 = Date.now());
dumpn(
"*** init_HandleAsync2: start time " + now);
}
function start_handleAsync2(ch) {
var now = Date.now();
dumpn(
"*** start_handleAsync2: onStartRequest time " +
now +
", " +
(now - startTime_handleAsync2) +
"ms after start time"
);
Assert.ok(now >= startTime_handleAsync2 + startToHeaderDelay);
Assert.equal(ch.responseStatus, 200);
Assert.equal(ch.responseStatusText,
"Status line");
Assert.equal(ch.getResponseHeader(
"X-Custom-Header"),
"value");
}
function stop_handleAsync2(ch, status, data) {
var now = Date.now();
dumpn(
"*** stop_handleAsync2: onStopRequest time " +
now +
", " +
(now - startTime_handleAsync2) +
"ms after header time"
);
Assert.ok(now >= startTime_handleAsync2 + startToFinishedDelay);
Assert.equal(String.fromCharCode.apply(
null, data),
"BODY");
}
/*
* Tests that accessing output stream *before* calling processAsync() works
* correctly, sending written data immediately as it is written, not buffering
* until finish() is called -- which for this much data would mean we would all
* but certainly deadlock, since we're trying to read/write all this data in one
* process on a single thread.
*/
function handleAsyncOrdering(request, response) {
var out =
new BinaryOutputStream(response.bodyOutputStream);
var data = [];
for (
var i = 0; i < 65536; i++) {
data[i] = 0;
}
var count = 20;
var writeData = {
run() {
if (count-- === 0) {
response.finish();
return;
}
try {
out.writeByteArray(data);
step();
}
catch (e) {
try {
do_throw(
"error writing data: " + e);
}
finally {
response.finish();
}
}
},
};
function step() {
// Use gThreadManager here because it's expedient, *not* because it's
// intended for public use! If you do this in client code, expect me to
// knowingly break your code by changing the variable name. :-P
Services.tm.dispatchToMainThread(writeData);
}
step();
response.processAsync();
}
handlers[
"/handleAsyncOrdering"] = handleAsyncOrdering;
function stop_handleAsyncOrdering(ch, status, data) {
Assert.equal(data.length, 20 * 65536);
data.forEach(
function (v, index) {
if (v !== 0) {
do_throw(
"value " + v +
" at index " + index +
" should be zero");
}
});
}