Bug 1362992 - Wrap implementation errors as UnknownError draft
authorAndreas Tolfsen <ato@mozilla.com>
Mon, 08 May 2017 14:01:30 +0100
changeset 576906 a27778896a5b4a56fb08a7146b6ef16999eb0d1e
parent 576897 1178b701781de2b1a5afb7b7d6b4954a3a7a51ba
child 628345 94c65f61bfc1c6b508a6915c48413681ae65afb2
push id58519
push userbmo:ato@mozilla.com
push dateFri, 12 May 2017 13:20:06 +0000
bugs1362992
milestone55.0a1
Bug 1362992 - Wrap implementation errors as UnknownError JavaScript errors that arise from the Marionette implementation are currently wrapped as WebDriverErrors. A WebDriverError is encoded with a "webdriver error" error code, which officially does not exist in the WebDriver specification. To distinguish WebDriver errors from programming errors of Marionette, this changes them to be encoded as UnknownErrors (error code "unknown error"). This will make stacktraces coming from clients easier to read, since it is clear that the error is not caught by the WebDriverError-catchall. MozReview-Commit-ID: A5HejTOgOp8
testing/marionette/error.js
testing/marionette/test_error.js
testing/marionette/test_message.js
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -91,25 +91,34 @@ error.isError = function (val) {
  * Checks if obj is an object in the WebDriverError prototypal chain.
  */
 error.isWebDriverError = function (obj) {
   return error.isError(obj) &&
       ("name" in obj && ERRORS.has(obj.name));
 };
 
 /**
- * Wraps any error as a WebDriverError.  If the given error is already in
- * the WebDriverError prototype chain, this function returns it
- * unmodified.
+ * Ensures error instance is a WebDriverError.
+ *
+ * If the given error is already in the WebDriverError prototype
+ * chain, |err| is returned unmodified.  If it is not, it is wrapped
+ * in UnknownError.
+ *
+ * @param {Error} err
+ *     Error to conditionally turn into a WebDriverError.
+ *
+ * @return {WebDriverError}
+ *     If |err| is a WebDriverError, it is returned unmodified.
+ *     Otherwise an UnknownError type is returned.
  */
 error.wrap = function (err) {
   if (error.isWebDriverError(err)) {
     return err;
   }
-  return new WebDriverError(err);
+  return new UnknownError(err);
 };
 
 /**
  * Unhandled error reporter.  Dumps the error and its stacktrace to console,
  * and reports error to the Browser Console.
  */
 error.report = function (err) {
   let msg = "Marionette threw an error: " + error.stringify(err);
--- a/testing/marionette/test_error.js
+++ b/testing/marionette/test_error.js
@@ -51,26 +51,26 @@ add_test(function test_isWebDriverError(
 add_test(function test_wrap() {
   // webdriver-derived errors should not be wrapped
   equal(error.wrap(new WebDriverError()).name, "WebDriverError");
   ok(error.wrap(new WebDriverError()) instanceof WebDriverError);
   equal(error.wrap(new InvalidArgumentError()).name, "InvalidArgumentError");
   ok(error.wrap(new InvalidArgumentError()) instanceof WebDriverError);
   ok(error.wrap(new InvalidArgumentError()) instanceof InvalidArgumentError);
 
-  // JS errors should be wrapped in WebDriverError
-  equal(error.wrap(new Error()).name, "WebDriverError");
-  ok(error.wrap(new Error()) instanceof WebDriverError);
-  equal(error.wrap(new EvalError()).name, "WebDriverError");
-  equal(error.wrap(new InternalError()).name, "WebDriverError");
-  equal(error.wrap(new RangeError()).name, "WebDriverError");
-  equal(error.wrap(new ReferenceError()).name, "WebDriverError");
-  equal(error.wrap(new SyntaxError()).name, "WebDriverError");
-  equal(error.wrap(new TypeError()).name, "WebDriverError");
-  equal(error.wrap(new URIError()).name, "WebDriverError");
+  // JS errors should be wrapped in UnknownError
+  equal(error.wrap(new Error()).name, "UnknownError");
+  ok(error.wrap(new Error()) instanceof UnknownError);
+  equal(error.wrap(new EvalError()).name, "UnknownError");
+  equal(error.wrap(new InternalError()).name, "UnknownError");
+  equal(error.wrap(new RangeError()).name, "UnknownError");
+  equal(error.wrap(new ReferenceError()).name, "UnknownError");
+  equal(error.wrap(new SyntaxError()).name, "UnknownError");
+  equal(error.wrap(new TypeError()).name, "UnknownError");
+  equal(error.wrap(new URIError()).name, "UnknownError");
 
   // wrapped JS errors should retain their type
   // as part of the message field
   equal(error.wrap(new WebDriverError("foo")).message, "foo");
   equal(error.wrap(new TypeError("foo")).message, "TypeError: foo");
 
   run_next_test();
 });
--- a/testing/marionette/test_message.js
+++ b/testing/marionette/test_message.js
@@ -145,30 +145,61 @@ add_test(function test_Response_send() {
   let resp = new Response(42, () => fired = true);
   resp.send();
   equal(true, resp.sent);
   equal(true, fired);
 
   run_next_test();
 });
 
-add_test(function test_Response_sendError() {
-  let err = new WebDriverError();
+add_test(function test_Response_sendError_sent() {
+  let resp = new Response(42, r => equal(false, r.sent));
+  resp.sendError(new WebDriverError());
+  ok(resp.sent);
+  Assert.throws(() => resp.send(), /already been sent/);
+
+  run_next_test();
+});
+
+add_test(function test_Response_sendError_body() {
+  let resp = new Response(42, r => equal(null, r.body));
+  resp.sendError(new WebDriverError());
+
+  run_next_test();
+});
+
+add_test(function test_Response_sendError_errorSerialisation() {
+  let err1 = new WebDriverError();
+  let resp1 = new Response(42);
+  resp1.sendError(err1);
+  equal(err1.status, resp1.error.error);
+  deepEqual(err1.toJSON(), resp1.error);
+
+  let err2 = new InvalidArgumentError();
+  let resp2 = new Response(43);
+  resp2.sendError(err2);
+  equal(err2.status, resp2.error.error);
+  deepEqual(err2.toJSON(), resp2.error);
+
+  run_next_test();
+});
+
+add_test(function test_Response_sendError_wrapInternalError() {
+  let err = new ReferenceError("foo");
+
+  // errors that originate from JavaScript (i.e. Marionette implementation
+  // issues) should be converted to UnknownError for transport
   let resp = new Response(42, r => {
-    equal(err.toJSON().error, r.error.error);
-    equal(null, r.body);
-    equal(false, r.sent);
+    equal("unknown error", r.error.error);
+    equal(false, resp.sent);
   });
 
-  resp.sendError(err);
+  // they should also throw after being sent
+  Assert.throws(() => resp.sendError(err), /foo/);
   equal(true, resp.sent);
-  Assert.throws(() => resp.send(), /already been sent/);
-
-  resp.sent = false;
-  Assert.throws(() => resp.sendError(new Error()));
 
   run_next_test();
 });
 
 add_test(function test_Response_toMsg() {
   let resp = new Response(42, () => {});
   let msg = resp.toMsg();