Bug 1142772 - Interleave the DOM Cache tests run through the mini-framework; r=bkelly

Here are the changes needed to achieve this:

* We tag each message posted to the framework with a context argument.
  context can be one of "Window", "Worker" or "ServiceWorker".  In the
  places where we handle messages, we only handle the ones corresponding
  to the context that we expect.
* We use Promise.all in order to interleave the execution of the tests.
  This has the nice property of actually getting the tests run
  simultaneously on multi-core machines.
* For ease of debugging test failures, we print the context from which
  each test message is coming from.
* In order for the tests that are run in parallel to not step on each
  other's toes, we introduce a global `context' variable that the test
  script can use to create names that do not clash with concurrent
  test runs.  For example, instead of `caches.open("foo")', one must now
  use: `caches.open("foo" + context)'.
* The existing tests are modified based on the above.
* When running the tests in parallel, the service worker may end up
  controlling both frame.html and message_receiver.html.  The hunk at
  the end of worker_wrapper.js is intended to ensure that we will
  always be communicating with message_receiver.html.
* An order argument has been added to runTests which can be set to
  "sequential" in order to run the tests in different contexts in
  parallel.  If the argument is omitted, the default is "parallel".
This commit is contained in:
Ehsan Akhgari 2015-03-12 14:38:03 -04:00
Родитель 43c73c2bcf
Коммит eed5a8dba4
8 изменённых файлов: 112 добавлений и 57 удалений

53
dom/cache/test/mochitest/driver.js поставляемый
Просмотреть файл

@ -8,10 +8,15 @@
// 2. Service Worker context
// 3. Window context
// The function returns a promise which will get resolved once all tests
// finish. The caller of this function is responsible to call SimpleTest.finish
// finish. The testFile argument is the name of the test file to be run
// in the different contexts, and the optional order argument can be set
// to either "parallel" or "sequential" depending on how the caller wants
// the tests to be run. If this argument is not provided, the default is
// "both", which runs the tests in both modes.
// The caller of this function is responsible to call SimpleTest.finish
// when the returned promise is resolved.
function runTests(testFile) {
function runTests(testFile, order) {
function setupPrefs() {
return new Promise(function(resolve, reject) {
SpecialPowers.pushPrefEnv({
@ -56,14 +61,17 @@ function runTests(testFile) {
var doc = iframe.contentDocument;
var s = doc.createElement("script");
s.src = testFile;
window.onmessage = function(event) {
window.addEventListener("message", function onMessage(event) {
if (event.data.context != "Window") {
return;
}
if (event.data.type == 'finish') {
window.onmessage = null;
window.removeEventListener("message", onMessage);
resolve();
} else if (event.data.type == 'status') {
ok(event.data.status, event.data.msg);
ok(event.data.status, event.data.context + ": " + event.data.msg);
}
};
}, false);
doc.body.appendChild(s);
};
document.body.appendChild(iframe);
@ -71,14 +79,35 @@ function runTests(testFile) {
}
SimpleTest.waitForExplicitFinish();
if (typeof order == "undefined") {
order = "both"; // both by default
}
ok(order == "parallel" || order == "sequential" || order == "both",
"order argument should be valid");
if (order == "both") {
info("Running tests in both modes; first: sequential");
return runTests(testFile, "sequential")
.then(function() {
info("Running tests in parallel mode");
return runTests(testFile, "parallel");
});
}
if (order == "sequential") {
return setupPrefs()
.then(importDrivers)
.then(runWorkerTest)
.then(runServiceWorkerTest)
.then(runFrameTest)
.catch(function(e) {
ok(false, "A promise was rejected during test execution: " + e);
});
}
return setupPrefs()
.then(importDrivers)
// TODO: Investigate interleaving these tests by making the driver able
// to differentiate where the incoming messages are coming from and use
// Promise.all([runWorkerTest, runServiceWorkerTest, runFrameTest]t) below.
.then(runWorkerTest)
.then(runServiceWorkerTest)
.then(runFrameTest)
.then(() => Promise.all([runWorkerTest(), runServiceWorkerTest(), runFrameTest()]))
.catch(function(e) {
ok(false, "A promise was rejected during test execution: " + e);
});

11
dom/cache/test/mochitest/frame.html поставляемый
Просмотреть файл

@ -1,16 +1,17 @@
<!DOCTYPE html>
<script>
var context = "Window";
function ok(a, msg) {
dump("OK: " + !!a + " => " + a + ": " + msg + "\n");
parent.postMessage({type: 'status', status: !!a, msg: a + ": " + msg }, "*");
parent.postMessage({type: 'status', status: !!a,
msg: a + ": " + msg, context: context}, "*");
}
function is(a, b, msg) {
dump("IS: " + (a===b) + " => " + a + " | " + b + ": " + msg + "\n");
parent.postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }, "*");
parent.postMessage({type: 'status', status: a === b,
msg: a + " === " + b + ": " + msg, context: context}, "*");
}
function testDone() {
parent.postMessage({type: 'finish'}, "*");
parent.postMessage({type: 'finish', context: context}, "*");
}
</script>

Просмотреть файл

@ -14,16 +14,19 @@ function serviceWorkerTestExec(testFile) {
var worker = registration.waiting ||
registration.active;
window.onmessage = function(event) {
window.addEventListener("message",function onMessage(event) {
if (event.data.context != "ServiceWorker") {
return;
}
if (event.data.type == 'finish') {
window.onmessage = null;
window.removeEventListener("message", onMessage);
registration.unregister()
.then(resolve)
.catch(reject);
} else if (event.data.type == 'status') {
ok(event.data.status, event.data.msg);
ok(event.data.status, event.data.context + ": " + event.data.msg);
}
};
}, false);
worker.onerror = reject;

40
dom/cache/test/mochitest/test_cache.js поставляемый
Просмотреть файл

@ -1,32 +1,34 @@
var c = null
var request = "http://example.com/hmm?q=foobar";
var request = "http://example.com/hmm?q=foobar" + context;
var response = new Response("This is some Response!");
var name = 'snafu' + context;
var foobar = 'foobar' + context;
ok(!!caches, 'caches object should be available on global');
caches.open('snafu').then(function(openCache) {
caches.open(name).then(function(openCache) {
ok(openCache instanceof Cache, 'cache object should be resolved from caches.open');
return caches.has('snafu');
return caches.has(name);
}).then(function(hasResult) {
ok(hasResult, 'caches.has() should resolve true');
return caches.keys();
}).then(function(keys) {
ok(!!keys, 'caches.keys() should resolve to a truthy value');
is(1, keys.length, 'caches.keys() should resolve to an array of length 1');
is(0, keys.indexOf('snafu'), 'caches.keys() should resolve to an array containing key');
return caches.delete('snafu');
ok(keys.length >= 1, 'caches.keys() should resolve to an array of length at least 1');
ok(keys.indexOf(name) >= 0, 'caches.keys() should resolve to an array containing key');
return caches.delete(name);
}).then(function(deleteResult) {
ok(deleteResult, 'caches.delete() should resolve true');
return caches.has('snafu');
return caches.has(name);
}).then(function(hasMissingCache) {
ok(!hasMissingCache, 'missing key should return false from has');
}).then(function() {
return caches.open('snafu');
return caches.open(name);
}).then(function(snafu) {
return snafu.keys();
}).then(function(empty) {
is(0, empty.length, 'cache.keys() should resolve to an array of length 0');
}).then(function() {
return caches.open('snafu');
return caches.open(name);
}).then(function(snafu) {
var req = './cachekey';
var res = new Response("Hello world");
@ -65,7 +67,7 @@ caches.open('snafu').then(function(openCache) {
// FIXME(nsm): Can't use a Request object for now since the operations
// consume it's 'body'. See
// https://github.com/slightlyoff/ServiceWorker/issues/510.
return caches.open("foobar");
return caches.open(foobar);
}).then(function(openCache) {
c = openCache;
return c.put(request, response);
@ -89,10 +91,10 @@ caches.open('snafu').then(function(openCache) {
return caches.match(request);
}).then(function(storageMatchResponse) {
ok(storageMatchResponse, 'storage match should succeed');
return caches.match(request, {cacheName:"foobar"});
return caches.match(request, {cacheName:foobar});
}).then(function(storageMatchResponse) {
ok(storageMatchResponse, 'storage match should succeed');
var request2 = new Request("http://example.com/hmm?q=snafu");
ok(storageMatchResponse, 'storage match with cacheName should succeed');
var request2 = new Request("http://example.com/hmm?q=snafu" + context);
return c.match(request2, {ignoreSearch:true});
}).then(function(match2Response) {
ok(match2Response, 'match should succeed');
@ -107,19 +109,19 @@ caches.open('snafu').then(function(openCache) {
}).then(function(matchAll2Responses) {
ok(matchAll2Responses, 'matchAll should succeed');
is(matchAll2Responses.length, 0, 'Zero matches is expected');
return caches.has("foobar");
return caches.has(foobar);
}).then(function(hasResult) {
ok(hasResult, 'has should succeed');
return caches.keys();
}).then(function(keys) {
ok(keys, 'Valid keys object expected');
is(keys.length, 2, 'Two keys are expected');
is(keys.indexOf("snafu"), 0, 'snafu should be the first key');
is(keys.indexOf("foobar"), 1, 'foobar should be the second key');
return caches.delete("foobar");
ok(keys.length >= 2, 'At least two keys are expected');
ok(keys.indexOf(name) >= 0, 'snafu should exist');
ok(keys.indexOf(foobar) >= keys.indexOf(name), 'foobar should come after it');
return caches.delete(foobar);
}).then(function(deleteResult) {
ok(deleteResult, 'delete should succeed');
return caches.has("foobar");
return caches.has(foobar);
}).then(function(hasMissingCache) {
ok(!hasMissingCache, 'has should have a result');
}).then(function() {

9
dom/cache/test/mochitest/test_cache_add.js поставляемый
Просмотреть файл

@ -5,12 +5,13 @@ var urlList = [
'./test_cache.js'
];
var cache;
caches.open('adder').then(function(openCache) {
var name = "adder" + context;
caches.open(name).then(function(openCache) {
cache = openCache;
return cache.add('ftp://example.com/invalid');
return cache.add('ftp://example.com/invalid' + context);
}).catch(function (err) {
is(err.name, 'NetworkError', 'add() should throw NetworkError for invalid scheme');
return cache.addAll(['http://example.com/valid', 'ftp://example.com/invalid']);
return cache.addAll(['http://example.com/valid' + context, 'ftp://example.com/invalid' + context]);
}).catch(function (err) {
is(err.name, 'NetworkError', 'addAll() should throw NetworkError for invalid scheme');
var promiseList = urlList.map(function(url) {
@ -45,7 +46,7 @@ caches.open('adder').then(function(openCache) {
resultList.every(function(result) {
ok(!!result, 'Responses should now be in cache for each URL.');
});
return caches.delete("adder");
return caches.delete(name);
}).then(function() {
testDone();
}).catch(function(err) {

Просмотреть файл

@ -1,7 +1,8 @@
var request = new Request("//mochi.test:8888/");
var request = new Request("//mochi.test:8888/?" + context);
var response;
var c;
var responseText;
var name = "match-request" + context;
function checkResponse(r) {
ok(r !== response, "The objects should not be the same");
@ -21,7 +22,7 @@ fetch(new Request(request)).then(function(r) {
return response.text();
}).then(function(text) {
responseText = text;
return caches.open("match-request");
return caches.open(name);
}).then(function(cache) {
c = cache;
return c.add(request);
@ -34,15 +35,15 @@ fetch(new Request(request)).then(function(r) {
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return caches.match(request, {cacheName: "match-request"});
return caches.match(request, {cacheName: name});
}).then(function(r) {
return checkResponse(r);
}).then(function() {
return caches.match(request, {cacheName: "foobar"});
return caches.match(request, {cacheName: name + "mambojambo"});
}).catch(function(err) {
is(err.name, "NotFoundError", "Searching in the wrong cache should not succeed");
}).then(function() {
return caches.delete("match-request");
return caches.delete(name);
}).then(function(success) {
ok(success, "We should be able to delete the cache successfully");
// Make sure that the cache is still usable after deletion.
@ -52,7 +53,7 @@ fetch(new Request(request)).then(function(r) {
}).then(function() {
// Now, drop the cache, reopen and verify that we can't find the request any more.
c = null;
return caches.open("match-request");
return caches.open(name);
}).then(function(cache) {
return cache.match(request);
}).catch(function(err) {

4
dom/cache/test/mochitest/worker_driver.js поставляемый
Просмотреть файл

@ -30,12 +30,14 @@ function workerTestExec(script) {
return new Promise(function(resolve, reject) {
var worker = new Worker('worker_wrapper.js');
worker.onmessage = function(event) {
is(event.data.context, "Worker",
"Correct context for messages received on the worker");
if (event.data.type == 'finish') {
SpecialPowers.forceGC();
resolve();
} else if (event.data.type == 'status') {
ok(event.data.status, event.data.msg);
ok(event.data.status, event.data.context + ": " + event.data.msg);
} else if (event.data.type == 'getPrefs') {
var result = {};

28
dom/cache/test/mochitest/worker_wrapper.js поставляемый
Просмотреть файл

@ -4,15 +4,16 @@
// ServiceWorker equivalent of worker_wrapper.js.
var client;
var context;
function ok(a, msg) {
dump("OK: " + !!a + " => " + a + ": " + msg + "\n");
client.postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
client.postMessage({type: 'status', status: !!a,
msg: a + ": " + msg, context: context});
}
function is(a, b, msg) {
dump("IS: " + (a===b) + " => " + a + " | " + b + ": " + msg + "\n");
client.postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
client.postMessage({type: 'status', status: a === b,
msg: a + " === " + b + ": " + msg, context: context });
}
function workerTestArrayEquals(a, b) {
@ -28,7 +29,7 @@ function workerTestArrayEquals(a, b) {
}
function testDone() {
client.postMessage({ type: 'finish' });
client.postMessage({ type: 'finish', context: context });
}
function workerTestGetPrefs(prefs, cb) {
@ -42,6 +43,7 @@ function workerTestGetPrefs(prefs, cb) {
});
client.postMessage({
type: 'getPrefs',
context: context,
prefs: prefs
});
}
@ -57,6 +59,7 @@ function workerTestGetPermissions(permissions, cb) {
});
client.postMessage({
type: 'getPermissions',
context: context,
permissions: permissions
});
}
@ -70,6 +73,7 @@ function workerTestGetVersion(cb) {
cb(e.data.result);
});
client.postMessage({
context: context,
type: 'getVersion'
});
}
@ -83,6 +87,7 @@ function workerTestGetUserAgent(cb) {
cb(e.data.result);
});
client.postMessage({
context: context,
type: 'getUserAgent'
});
}
@ -97,17 +102,28 @@ addEventListener('message', function workerWrapperOnMessage(e) {
client.postMessage({
type: 'status',
status: false,
context: context,
msg: 'worker failed to import ' + data.script + "; error: " + e.message
});
}
}
if ("ServiceWorker" in self) {
self.clients.matchAll().then(function(clients) {
client = clients[0];
for (var i = 0; i < clients.length; ++i) {
if (clients[i].url.indexOf("message_receiver.html") > -1) {
client = clients[i];
break;
}
}
if (!client) {
dump("We couldn't find the message_receiver window, the test will fail\n");
}
context = "ServiceWorker";
runScript();
});
} else {
client = self;
context = "Worker";
runScript();
}
});