Bug 1447977 - Move cyclic object test function to evaluate. r?automatedtester
Moves the innards of assert.acyclic to evaluate.isCyclic so it can
be called externally without throwing. It makes more sense for
this function to be exposed on the evaluate module, since other
behaviour related to JSON marshaling is also defined there.
MozReview-Commit-ID: 4WjEy8Sjqrm
--- a/testing/marionette/assert.js
+++ b/testing/marionette/assert.js
@@ -14,55 +14,51 @@ const {
InvalidSessionIDError,
JavaScriptError,
NoSuchWindowError,
UnexpectedAlertOpenError,
UnsupportedOperationError,
} = ChromeUtils.import("chrome://marionette/content/error.js", {});
const {pprint} = ChromeUtils.import("chrome://marionette/content/format.js", {});
-XPCOMUtils.defineLazyGetter(this, "browser", () => {
- const {browser} = ChromeUtils.import("chrome://marionette/content/browser.js", {});
- return browser;
+XPCOMUtils.defineLazyModuleGetters(this, {
+ evaluate: "chrome://marionette/content/evaluate.js",
+ browser: "chrome://marionette/content/browser.js",
});
this.EXPORTED_SYMBOLS = ["assert"];
const isFennec = () => AppConstants.platform == "android";
const isFirefox = () =>
Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
/**
* Shorthands for common assertions made in Marionette.
*
* @namespace
*/
this.assert = {};
/**
- * Asserts that an arbitrary object, <var>obj</var> is not acyclic.
+ * Asserts that an arbitrary object is not acyclic.
*
* @param {*} obj
- * Object test. This assertion is only meaningful if passed
+ * Object to 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>.
+ * Custom message to use for `error` if assertion fails.
*
* @throws {JavaScriptError}
- * If <var>obj</var> is cyclic.
+ * If the object is cyclic.
*/
assert.acyclic = function(obj, msg = "", error = JavaScriptError) {
- try {
- JSON.stringify(obj);
- } catch (e) {
- throw new error(msg || e);
+ if (evaluate.isCyclic(obj)) {
+ throw new error(msg || "Cyclic object value");
}
};
/**
* Asserts that Marionette has a session.
*
* @param {GeckoDriver} driver
* Marionette driver instance.
--- a/testing/marionette/evaluate.js
+++ b/testing/marionette/evaluate.js
@@ -279,16 +279,34 @@ evaluate.toJSON = function(obj, seenEls)
throw e;
}
}
}
return rv;
};
/**
+ * Tests if an arbitrary object is cyclic.
+ *
+ * @param {*} obj
+ * Object to test for cyclical references.
+ *
+ * @return {boolean}
+ * True if object is cyclic, false otherwise.
+ */
+evaluate.isCyclic = function(obj) {
+ try {
+ JSON.stringify(obj);
+ return false;
+ } catch (e) {
+ return true;
+ }
+};
+
+/**
* `Cu.isDeadWrapper` does not return true for a dead sandbox that
* was assosciated with and extension popup. This provides a way to
* still test for a dead object.
*
* @param {Object} obj
* A potentially dead object.
* @param {string} prop
* Name of a property on the object.
--- a/testing/marionette/test/unit/test_assert.js
+++ b/testing/marionette/test/unit/test_assert.js
@@ -13,48 +13,23 @@ const {
NoSuchWindowError,
SessionNotCreatedError,
UnexpectedAlertOpenError,
UnsupportedOperationError,
} = ChromeUtils.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();
--- a/testing/marionette/test/unit/test_evaluate.js
+++ b/testing/marionette/test/unit/test_evaluate.js
@@ -113,8 +113,48 @@ add_test(function test_toJSON_objects()
equal(true, actual.boolean);
deepEqual([], actual.array);
ok(WebElement.isReference(actual.webElement));
equal("foo", actual.toJSON);
deepEqual({"bar": "baz"}, actual.object);
run_next_test();
});
+
+add_test(function test_isCyclic_noncyclic() {
+ for (let type of [true, 42, "foo", [], {}, null, undefined]) {
+ ok(!evaluate.isCyclic(type));
+ }
+
+ run_next_test();
+});
+
+add_test(function test_isCyclic_object() {
+ let obj = {};
+ obj.reference = obj;
+ ok(evaluate.isCyclic(obj));
+
+ run_next_test();
+});
+
+add_test(function test_isCyclic_array() {
+ let arr = [];
+ arr.push(arr);
+ ok(evaluate.isCyclic(arr));
+
+ run_next_test();
+});
+
+add_test(function test_isCyclic_arrayInObject() {
+ let arr = [];
+ arr.push(arr);
+ ok(evaluate.isCyclic({arr}));
+
+ run_next_test();
+});
+
+add_test(function test_isCyclic_objectInArray() {
+ let obj = {};
+ obj.reference = obj;
+ ok(evaluate.isCyclic([obj]));
+
+ run_next_test();
+});