Bug 1106913 - Add assert.acyclic for testing for cyclic objects. r?whimboo draft
authorAndreas Tolfsen <ato@sny.no>
Fri, 24 Nov 2017 18:21:03 +0000
changeset 705706 51ac14899f3d2352e95def6dc2237d6719f1a979
parent 705705 be6fc3b4c73fa527722bcef517361d3e1469be40
child 705707 81e7e05b54504575df52b3cbbbe032f3a7de446a
push id91554
push userbmo:ato@sny.no
push dateThu, 30 Nov 2017 15:13:32 +0000
reviewerswhimboo
bugs1106913
milestone59.0a1
Bug 1106913 - Add assert.acyclic for testing for cyclic objects. r?whimboo Introduces a new assert.acyclic assertion helper function that uses JSON.stringify to test if the input contains cyclic object references. A JavaScriptError will be thrown if the object is not acyclic. MozReview-Commit-ID: 3y8pnkPgf2k
testing/marionette/assert.js
testing/marionette/test_assert.js
--- a/testing/marionette/assert.js
+++ b/testing/marionette/assert.js
@@ -8,16 +8,17 @@ const {utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 const {
   InvalidArgumentError,
   InvalidSessionIDError,
+  JavaScriptError,
   NoSuchWindowError,
   UnexpectedAlertOpenError,
   UnsupportedOperationError,
 } = Cu.import("chrome://marionette/content/error.js", {});
 const {pprint} = Cu.import("chrome://marionette/content/format.js", {});
 
 this.EXPORTED_SYMBOLS = ["assert"];
 
@@ -28,16 +29,40 @@ const isFirefox = () =>
 /**
  * Shorthands for common assertions made in Marionette.
  *
  * @namespace
  */
 this.assert = {};
 
 /**
+ * Asserts that an arbitrary object, <var>obj</var> is not acyclic.
+ *
+ * @param {*} obj
+ *     Object test.  This assertion is only meaningful if passed
+ *     an actual object or array.
+ * @param {Error=} [error=JavaScriptError] error
+ *     Error to throw if assertion fails.
+ * @param {string=} message
+ *     Message to use for <var>error</var> if assertion fails.  By default
+ *     it will use the error message provided by
+ *     <code>JSON.stringify</code>.
+ *
+ * @throws {JavaScriptError}
+ *     If <var>obj</var> is cyclic.
+ */
+assert.acyclic = function(obj, msg = "", error = JavaScriptError) {
+  try {
+    JSON.stringify(obj);
+  } catch (e) {
+    throw new error(msg || e);
+  }
+};
+
+/**
  * Asserts that Marionette has a session.
  *
  * @param {GeckoDriver} driver
  *     Marionette driver instance.
  * @param {string=} msg
  *     Custom error message.
  *
  * @return {string}
--- a/testing/marionette/test_assert.js
+++ b/testing/marionette/test_assert.js
@@ -4,16 +4,60 @@
 
 "use strict";
 
 const {utils: Cu} = Components;
 
 Cu.import("chrome://marionette/content/assert.js");
 Cu.import("chrome://marionette/content/error.js");
 
+add_test(function test_acyclic() {
+  assert.acyclic({});
+  assert.acyclic(new Object());
+  assert.acyclic([]);
+  assert.acyclic(new Array());
+
+  // object
+  Assert.throws(() => {
+    let obj = {};
+    obj.reference = obj;
+    assert.acyclic(obj);
+  }, JavaScriptError);
+
+  // array
+  Assert.throws(() => {
+    let arr = [];
+    arr.push(arr);
+    assert.acyclic(arr);
+  }, JavaScriptError);
+
+  // array in object
+  Assert.throws(() => {
+    let arr = [];
+    arr.push(arr);
+    assert.acyclic({arr});
+  }, JavaScriptError);
+
+  // object in array
+  Assert.throws(() => {
+    let obj = {};
+    obj.reference = obj;
+    assert.acyclic([obj]);
+  }, JavaScriptError);
+
+  // custom message
+  let cyclic = {};
+  cyclic.reference = cyclic;
+  Assert.throws(() => assert.acyclic(cyclic, "", RangeError), RangeError);
+  Assert.throws(() => assert.acyclic(cyclic, "foo"), /JavaScriptError: foo/);
+  Assert.throws(() => assert.acyclic(cyclic, "bar", RangeError), /RangeError: bar/);
+
+  run_next_test();
+});
+
 add_test(function test_session() {
   assert.session({sessionID: "foo"});
   for (let typ of [null, undefined, ""]) {
     Assert.throws(() => assert.session({sessionId: typ}), InvalidSessionIDError);
   }
 
   Assert.throws(() => assert.session({sessionId: null}, "custom"), /custom/);