Bug 1637641: Add test for the cache mechanism in ResourceWatcher. r=ochameau
☠☠ backed out by 272e3c98d002 ☠ ☠
authorDaisuke Akatsuka <daisuke@birchill.co.jp>
Thu, 28 May 2020 09:07:17 +0000
changeset 532731 ce69538af12e6d3eadcd61bdb5f92a96749c939b
parent 532730 b6fabe7631779619cfda5f74a03a3eb1d242438e
child 532732 0c71a64381d269c3bbf2c320edb32e025c58c7fa
push id117344
push userdakatsuka.birchill@mozilla.com
push dateThu, 28 May 2020 09:09:15 +0000
treeherderautoland@ce69538af12e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1637641
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1637641: Add test for the cache mechanism in ResourceWatcher. r=ochameau Differential Revision: https://phabricator.services.mozilla.com/D77015
devtools/shared/resources/tests/browser.ini
devtools/shared/resources/tests/browser_resources_client_caching.js
devtools/shared/resources/tests/browser_resources_error_messages.js
devtools/shared/resources/tests/head.js
--- a/devtools/shared/resources/tests/browser.ini
+++ b/devtools/shared/resources/tests/browser.ini
@@ -6,16 +6,17 @@ support-files =
   !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   head.js
   fission_document.html
   fission_iframe.html
   test_service_worker.js
   test_worker.js
 
+[browser_resources_client_caching.js]
 [browser_resources_console_messages.js]
 [browser_resources_document_events.js]
 [browser_resources_error_messages.js]
 [browser_resources_platform_messages.js]
 [browser_resources_root_node.js]
 [browser_resources_several_resources.js]
 [browser_target_list_frames.js]
 [browser_target_list_getAllTargets.js]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/resources/tests/browser_resources_client_caching.js
@@ -0,0 +1,315 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the cache mechanism of the ResourceWatcher.
+
+const {
+  ResourceWatcher,
+} = require("devtools/shared/resources/resource-watcher");
+
+add_task(async function() {
+  info("Test whether multiple listener can get same cached resources");
+
+  const tab = await addTab("data:text/html,Cache Test");
+
+  const {
+    client,
+    resourceWatcher,
+    targetList,
+  } = await initResourceWatcherAndTarget(tab);
+
+  info("Add messages as existing resources");
+  const messages = ["a", "b", "c"];
+  await logMessages(tab.linkedBrowser, messages);
+
+  info("Register first listener");
+  const cachedResources1 = [];
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: ({ resource }) => cachedResources1.push(resource),
+    }
+  );
+
+  info("Register second listener");
+  const cachedResources2 = [];
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: ({ resource }) => cachedResources2.push(resource),
+    }
+  );
+
+  assertContents(cachedResources1, messages);
+  assertResources(cachedResources2, cachedResources1);
+
+  await targetList.stopListening();
+  await client.close();
+});
+
+add_task(async function() {
+  info(
+    "Test whether the cache is reflecting existing resources and additional resources"
+  );
+
+  const tab = await addTab("data:text/html,Cache Test");
+
+  const {
+    client,
+    resourceWatcher,
+    targetList,
+  } = await initResourceWatcherAndTarget(tab);
+
+  info("Add messages as existing resources");
+  const existingMessages = ["a", "b", "c"];
+  await logMessages(tab.linkedBrowser, existingMessages);
+
+  info("Register first listener to get all available resources");
+  const availableResources = [];
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: ({ resource }) => availableResources.push(resource),
+    }
+  );
+
+  info("Add messages as additional resources");
+  const additionalMessages = ["d", "e"];
+  await logMessages(tab.linkedBrowser, additionalMessages);
+
+  info("Wait until onAvailable is called expected times");
+  const allMessages = [...existingMessages, ...additionalMessages];
+  await waitUntil(() => availableResources.length === allMessages.length);
+
+  info("Register second listener to get the cached resources");
+  const cachedResources = [];
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: ({ resource }) => cachedResources.push(resource),
+    }
+  );
+
+  assertContents(availableResources, allMessages);
+  assertResources(cachedResources, availableResources);
+
+  await targetList.stopListening();
+  await client.close();
+});
+
+add_task(async function() {
+  info("Test whether the cache is cleared when navigation");
+
+  const tab = await addTab("data:text/html,Cache Test");
+
+  const {
+    client,
+    resourceWatcher,
+    targetList,
+  } = await initResourceWatcherAndTarget(tab);
+
+  info("Add messages as existing resources");
+  const existingMessages = ["a", "b", "c"];
+  await logMessages(tab.linkedBrowser, existingMessages);
+
+  info("Register first listener");
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: () => {},
+    }
+  );
+
+  info("Reload the page");
+  const onReloaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+  gBrowser.reloadTab(tab);
+  await onReloaded;
+
+  info("Register second listener");
+  const cachedResources = [];
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: ({ resource }) => cachedResources.push(resource),
+    }
+  );
+
+  is(cachedResources.length, 0, "The cache in ResourceWatcher is cleared");
+
+  await targetList.stopListening();
+  await client.close();
+});
+
+add_task(async function() {
+  info("Test with multiple resource types");
+
+  const tab = await addTab("data:text/html,Cache Test");
+
+  const {
+    client,
+    resourceWatcher,
+    targetList,
+  } = await initResourceWatcherAndTarget(tab);
+
+  info("Register first listener to get all available resources");
+  const availableResources = [];
+  await resourceWatcher.watchResources(
+    [
+      ResourceWatcher.TYPES.CONSOLE_MESSAGE,
+      ResourceWatcher.TYPES.ERROR_MESSAGE,
+    ],
+    {
+      onAvailable: ({ resource }) => availableResources.push(resource),
+    }
+  );
+
+  info("Add messages as console message");
+  const consoleMessages1 = ["a", "b", "c"];
+  await logMessages(tab.linkedBrowser, consoleMessages1);
+
+  info("Add message as error message");
+  const errorMessages = ["document.doTheImpossible();"];
+  await triggerErrors(tab.linkedBrowser, errorMessages);
+
+  info("Add messages as console message again");
+  const consoleMessages2 = ["d", "e"];
+  await logMessages(tab.linkedBrowser, consoleMessages2);
+
+  info("Register listener to get the cached resources");
+  const cachedResources = [];
+  await resourceWatcher.watchResources(
+    [
+      ResourceWatcher.TYPES.CONSOLE_MESSAGE,
+      ResourceWatcher.TYPES.ERROR_MESSAGE,
+    ],
+    {
+      onAvailable: ({ resource }) => cachedResources.push(resource),
+    }
+  );
+
+  is(
+    availableResources.length,
+    consoleMessages1.length + errorMessages.length + consoleMessages2.length,
+    "Number of the available resources is correct"
+  );
+  assertResources(cachedResources, availableResources);
+
+  await targetList.stopListening();
+  await client.close();
+});
+
+add_task(async function() {
+  info("Test multiple listeners with/without ignoreExistingResources");
+  await testIgnoreExistingResources(true);
+  await testIgnoreExistingResources(false);
+});
+
+async function testIgnoreExistingResources(isFirstListenerIgnoreExisting) {
+  const tab = await addTab("data:text/html,Cache Test");
+
+  const {
+    client,
+    resourceWatcher,
+    targetList,
+  } = await initResourceWatcherAndTarget(tab);
+
+  info("Add messages as existing resources");
+  const existingMessages = ["a", "b", "c"];
+  await logMessages(tab.linkedBrowser, existingMessages);
+
+  info("Register first listener");
+  const cachedResources1 = [];
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: ({ resource }) => cachedResources1.push(resource),
+      ignoreExistingResources: isFirstListenerIgnoreExisting,
+    }
+  );
+
+  info("Register second listener");
+  const cachedResources2 = [];
+  await resourceWatcher.watchResources(
+    [ResourceWatcher.TYPES.CONSOLE_MESSAGE],
+    {
+      onAvailable: ({ resource }) => cachedResources2.push(resource),
+      ignoreExistingResources: !isFirstListenerIgnoreExisting,
+    }
+  );
+
+  const cachedResourcesWithFlag = isFirstListenerIgnoreExisting
+    ? cachedResources1
+    : cachedResources2;
+  const cachedResourcesWithoutFlag = isFirstListenerIgnoreExisting
+    ? cachedResources2
+    : cachedResources1;
+
+  info("Check the existing resources both listeners got");
+  assertContents(cachedResourcesWithFlag, []);
+  assertContents(cachedResourcesWithoutFlag, existingMessages);
+
+  info("Add messages as additional resources");
+  const additionalMessages = ["d", "e"];
+  await logMessages(tab.linkedBrowser, additionalMessages);
+
+  info("Wait until onAvailable is called expected times");
+  await waitUntil(
+    () => cachedResourcesWithFlag.length === additionalMessages.length
+  );
+  const allMessages = [...existingMessages, ...additionalMessages];
+  await waitUntil(
+    () => cachedResourcesWithoutFlag.length === allMessages.length
+  );
+
+  info("Check the resources after adding messages");
+  assertContents(cachedResourcesWithFlag, additionalMessages);
+  assertContents(cachedResourcesWithoutFlag, allMessages);
+
+  await targetList.stopListening();
+  await client.close();
+}
+
+function assertContents(resources, expectedMessages) {
+  is(
+    resources.length,
+    expectedMessages.length,
+    "Number of the resources is correct"
+  );
+
+  for (let i = 0; i < expectedMessages.length; i++) {
+    const resource = resources[i];
+    const message = resource.message.arguments[0];
+    const expectedMessage = expectedMessages[i];
+    is(message, expectedMessage, `The ${i}th content is correct`);
+  }
+}
+
+function assertResources(resources, expectedResources) {
+  is(
+    resources.length,
+    expectedResources.length,
+    "Number of the resources is correct"
+  );
+
+  for (let i = 0; i < resources.length; i++) {
+    const resource = resources[i];
+    const expectedResource = expectedResources[i];
+    ok(resource === expectedResource, `The ${i}th resource is correct`);
+  }
+}
+
+function logMessages(browser, messages) {
+  return ContentTask.spawn(browser, { messages }, args => {
+    for (const message of args.messages) {
+      content.console.log(message);
+    }
+  });
+}
+
+async function triggerErrors(browser, errorScripts) {
+  for (const errorScript of errorScripts) {
+    await triggerError(browser, errorScript);
+  }
+}
--- a/devtools/shared/resources/tests/browser_resources_error_messages.js
+++ b/devtools/shared/resources/tests/browser_resources_error_messages.js
@@ -152,30 +152,17 @@ async function triggerErrors(tab) {
   for (const [expression, expected] of expectedPageErrors.entries()) {
     if (
       !expected[noUncaughtException] &&
       !Services.appinfo.browserTabsRemoteAutostart
     ) {
       expectUncaughtException();
     }
 
-    await ContentTask.spawn(tab.linkedBrowser, expression, function frameScript(
-      expr
-    ) {
-      const document = content.document;
-      const container = document.createElement("script");
-      document.body.appendChild(container);
-      container.textContent = expr;
-      container.remove();
-    });
-    // Wait a bit between each messages, as uncaught promises errors are not emitted
-    // right away.
-
-    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
-    await new Promise(res => setTimeout(res, 10));
+    await triggerError(tab.linkedBrowser, expression);
   }
 }
 
 const noUncaughtException = Symbol();
 
 const expectedPageErrors = new Map([
   [
     "document.doTheImpossible();",
--- a/devtools/shared/resources/tests/head.js
+++ b/devtools/shared/resources/tests/head.js
@@ -85,8 +85,26 @@ function checkValue(name, value, expecte
   } else if (Array.isArray(expected)) {
     info("checking array for property '" + name + "'");
     checkObject(value, expected);
   } else if (typeof expected == "object") {
     info("checking object for property '" + name + "'");
     checkObject(value, expected);
   }
 }
+
+/**
+ * Triggers a script error in the content page.
+ */
+async function triggerError(browser, expression) {
+  await ContentTask.spawn(browser, expression, function frameScript(expr) {
+    const document = content.document;
+    const container = document.createElement("script");
+    document.body.appendChild(container);
+    container.textContent = expr;
+    container.remove();
+  });
+  // Wait a bit between each messages, as uncaught promises errors are not emitted
+  // right away.
+
+  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+  await new Promise(res => setTimeout(res, 10));
+}