Bug 1596756 - Add browser tests for FinalizationGroup r=mccr8
authorJon Coppeard <jcoppeard@mozilla.com>
Wed, 15 Jan 2020 14:19:28 +0000
changeset 510357 c71d1ca78e2d703d786d159807fd84413f29c6c4
parent 510356 9db35ebe375907da4ccc704c2220fc012709e9f4
child 510358 d48d8dbcbbea2df29aa6600c6688c0c108f21189
push id37020
push userccoroiu@mozilla.com
push dateWed, 15 Jan 2020 21:36:21 +0000
treeherdermozilla-central@c35bb210b8ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1596756
milestone74.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 1596756 - Add browser tests for FinalizationGroup r=mccr8 These are mochitests because there's currently no way to trigger a GC from WPT. I tried to do this with xpcshell tests but I couldn't get the pref to enable weak refs to work. Differential Revision: https://phabricator.services.mozilla.com/D59375
js/xpconnect/tests/mochitest/finalizationGroup_worker.js
js/xpconnect/tests/mochitest/mochitest.ini
js/xpconnect/tests/mochitest/test_finalizationGroup.html
js/xpconnect/tests/mochitest/test_finalizationGroupInWorker.html
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/finalizationGroup_worker.js
@@ -0,0 +1,96 @@
+let holdings1 = [];
+let holdings2 = [];
+let holdings3 = [];
+let holdings4 = [];
+let holdings5 = [];
+
+onmessage = (event) => {
+  switch (event.data) {
+  case 'startTest':
+    startTest();
+    break;
+  case 'checkResults':
+    checkResults();
+    break;
+  default:
+    throw "Unknown message";
+  }
+};
+
+function startTest() {
+  // Group with no registered objects.
+  let group1 = new FinalizationGroup(i => holdings1 = [...i]);
+
+  // Group with three registered objects.
+  let group2 = new FinalizationGroup(i => holdings2 = [...i]);
+  group2.register({}, 1);
+  group2.register({}, 2);
+  group2.register({}, 3);
+
+  // Group with registered object that is then unregistered.
+  let group3 = new FinalizationGroup(i => holdings3 = [...i]);
+  let token3 = {}
+  group3.register({}, 1, token3);
+  group3.unregister(token3);
+
+  // Group with registered object that doesn't die.
+  let group4 = new FinalizationGroup(i => holdings4 = [...i]);
+  let object4 = {};
+  group4.register(object4, 1);
+
+  // Group observing cyclic JS data structure.
+  let group5 = new FinalizationGroup(i => holdings5 = [...i]);
+  group5.register(makeJSCycle(4), 5);
+
+  const { gc } = getJSTestingFunctions();
+  gc();
+
+  Promise.resolve().then(() => {
+    checkNoCallbacks();
+  });
+
+  postMessage('started');
+}
+
+function checkNoCallbacks() {
+  is(holdings1.length, 0);
+  is(holdings2.length, 0);
+  is(holdings3.length, 0);
+  is(holdings4.length, 0);
+  is(holdings5.length, 0);
+}
+
+function checkResults() {
+  is(holdings1.length, 0);
+
+  let result = holdings2.sort((a, b) => a - b);
+  is(result.length, 3);
+  is(result[0], 1);
+  is(result[1], 2);
+  is(result[2], 3);
+
+  is(holdings3.length, 0);
+  is(holdings4.length, 0);
+
+  is(holdings5.length, 1);
+  is(holdings5[0], 5);
+
+  postMessage('passed');
+}
+
+function is(a, b) {
+  if (a !== b) {
+    throw `Expected ${b} but got ${a}`;
+  }
+}
+
+function makeJSCycle(size) {
+  let first = {};
+  let current = first;
+  for (let i = 0; i < size; i++) {
+    current.next = {};
+    current = current.next;
+  }
+  current.next = first;
+  return first;
+}
--- a/js/xpconnect/tests/mochitest/mochitest.ini
+++ b/js/xpconnect/tests/mochitest/mochitest.ini
@@ -31,16 +31,19 @@ support-files =
   file_expandosharing.html
   file_matches.html
   file_nodelists.html
   file_wrappers-2.html
   file_xrayic.html
   inner.html
   test1_bug629331.html
   test2_bug629331.html
+  finalizationGroup_worker.js
+prefs =
+  javascript.options.experimental.weakrefs=true
 
 [test_bug384632.html]
 [test_bug390488.html]
 [test_bug393269.html]
 [test_bug396851.html]
 [test_bug428021.html]
 [test_bug446584.html]
 [test_bug462428.html]
@@ -105,8 +108,12 @@ skip-if = (debug == false)
 [test_paris_weakmap_keys.html]
 skip-if = (debug == false)
 [test_nukeContentWindow.html]
 [test_sameOriginPolicy.html]
 [test_sandbox_fetch.html]
   support-files =
     ../../../../dom/tests/mochitest/fetch/test_fetch_basic.js
 [test_weakmaps.html]
+[test_finalizationGroup.html]
+skip-if = !nightly_build
+[test_finalizationGroupInWorker.html]
+skip-if = !nightly_build
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/test_finalizationGroup.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Test FinalizationGroup works in the browser</title>
+    <script src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript">
+      function go() {
+        SimpleTest.waitForExplicitFinish();
+
+        // Group with no registered objects.
+        let holdings1 = [];
+        let group1 = new FinalizationGroup(i => holdings1 = [...i]);
+
+        // Group with three registered objects.
+        let holdings2 = [];
+        let group2 = new FinalizationGroup(i => holdings2 = [...i]);
+        group2.register({}, 1);
+        group2.register({}, 2);
+        group2.register({}, 3);
+
+        // Group with registered object that is then unregistered.
+        let holdings3 = [];
+        let group3 = new FinalizationGroup(i => holdings3 = [...i]);
+        let token3 = {}
+        group3.register({}, 1, token3);
+        group3.unregister(token3);
+
+        // Group with registered object that doesn't die.
+        let holdings4 = [];
+        let group4 = new FinalizationGroup(i => holdings4 = [...i]);
+        let object4 = {};
+        group4.register(object4, 1);
+
+        // Group observing cyclic JS data structure.
+        let holdings5 = [];
+        let group5 = new FinalizationGroup(i => holdings5 = [...i]);
+        group5.register(makeJSCycle(4), 5);
+
+        // Group observing cyclic DOM/JS data structure.
+        let holdings6 = [];
+        let group6 = new FinalizationGroup(i => holdings6 = [...i]);
+        group6.register(makeDOMCycle(4), 6);
+
+        // Need to run full GC/CC/GC cycle to collect cyclic garbage through DOM
+        // and JS heaps.
+        SpecialPowers.DOMWindowUtils.garbageCollect();
+        SpecialPowers.DOMWindowUtils.cycleCollect();
+        SpecialPowers.DOMWindowUtils.garbageCollect();
+
+        // Microtasks are run before cleanup callbacks.
+        Promise.resolve().then(() => {
+          is(holdings1.length, 0);
+          is(holdings2.length, 0);
+          is(holdings3.length, 0);
+          is(holdings4.length, 0);
+          is(holdings5.length, 0);
+          is(holdings6.length, 0);
+        });
+
+        // setTimeout queues a task which will run after cleanup callbacks.
+        setTimeout(() => {
+          is(holdings1.length, 0);
+
+          let result = holdings2.sort((a, b) => a - b);
+          is(result.length, 3);
+          is(result[0], 1);
+          is(result[1], 2);
+          is(result[2], 3);
+
+          is(holdings3.length, 0);
+          is(holdings4.length, 0);
+
+          is(holdings5.length, 1);
+          is(holdings5[0], 5);
+
+          is(holdings6.length, 1);
+          is(holdings6[0], 6);
+
+          SimpleTest.finish();
+        }, 0);
+      }
+
+      function makeJSCycle(size) {
+        let first = {};
+        let current = first;
+        for (let i = 0; i < size; i++) {
+          current.next = {};
+          current = current.next;
+        }
+        current.next = first;
+        return first;
+      }
+
+      function makeDOMCycle(size) {
+        let first = {};
+        let current = first;
+        for (let i = 0; i < size; i++) {
+          if (i % 2 === 0) {
+            current.next = document.createElement("div");
+          } else {
+            current.next = {};
+          }
+          current = current.next;
+        }
+        current.next = first;
+        return first;
+      }
+    </script>
+  </head>
+  <body onload="go()"></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/test_finalizationGroupInWorker.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Test FinalizationGroup works in workers</title>
+    <script src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript">
+      function go() {
+        SimpleTest.waitForExplicitFinish();
+
+        let worker = new Worker('finalizationGroup_worker.js');
+
+        worker.onevent = (event) => {
+          console.log(event.message);
+          throw event.error;
+        };
+
+        worker.onmessage = (event) => {
+          switch (event.data) {
+          case 'started':
+            worker.postMessage('checkResults');
+            break;
+
+          case 'passed':
+            ok(true, "Tests passed");
+            SimpleTest.finish();
+            break;
+
+          default:
+            console.log(event.data);
+            break;
+          }
+        };
+
+        worker.postMessage('startTest');
+      }
+    </script>
+  </head>
+  <body onload="go()"></body>
+</html>