forked from mirrors/gecko-dev
Before this would produce something like '..got "abc",null" which is rather ugly. Differential Revision: https://phabricator.services.mozilla.com/D108845
243 lines
6.2 KiB
JavaScript
243 lines
6.2 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "isXpcshell", function() {
|
|
let env = Cc["@mozilla.org/process/environment;1"].getService(
|
|
Ci.nsIEnvironment
|
|
);
|
|
return env.exists("XPCSHELL_TEST_PROFILE_DIR");
|
|
});
|
|
|
|
/**
|
|
* Checks whether the given error matches the given expectations.
|
|
*
|
|
* @param {*} error
|
|
* The error to check.
|
|
* @param {string|RegExp|function|null} expectedError
|
|
* The expectation to check against. If this parameter is:
|
|
*
|
|
* - a string, the error message must exactly equal the string.
|
|
* - a regular expression, it must match the error message.
|
|
* - a function, it is called with the error object and its
|
|
* return value is returned.
|
|
* @param {BaseContext} context
|
|
*
|
|
* @returns {boolean}
|
|
* True if the error matches the expected error.
|
|
*/
|
|
const errorMatches = (error, expectedError, context) => {
|
|
if (
|
|
typeof error === "object" &&
|
|
error !== null &&
|
|
!context.principal.subsumes(Cu.getObjectPrincipal(error))
|
|
) {
|
|
Cu.reportError("Error object belongs to the wrong scope.");
|
|
return false;
|
|
}
|
|
|
|
if (typeof expectedError === "function") {
|
|
return context.runSafeWithoutClone(expectedError, error);
|
|
}
|
|
|
|
if (
|
|
typeof error !== "object" ||
|
|
error == null ||
|
|
typeof error.message !== "string"
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (typeof expectedError === "string") {
|
|
return error.message === expectedError;
|
|
}
|
|
|
|
try {
|
|
return expectedError.test(error.message);
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Calls .toSource() on the given value, but handles null, undefined,
|
|
* and errors.
|
|
*
|
|
* @param {*} value
|
|
* @returns {string}
|
|
*/
|
|
const toSource = value => {
|
|
if (value === null) {
|
|
return "null";
|
|
}
|
|
if (value === undefined) {
|
|
return "undefined";
|
|
}
|
|
if (typeof value === "string") {
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
try {
|
|
return String(value);
|
|
} catch (e) {
|
|
return "<unknown>";
|
|
}
|
|
};
|
|
|
|
this.test = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
const { extension } = context;
|
|
|
|
function getStack() {
|
|
return new context.Error().stack.replace(/^/gm, " ");
|
|
}
|
|
|
|
function assertTrue(value, msg) {
|
|
extension.emit("test-result", Boolean(value), String(msg), getStack());
|
|
}
|
|
|
|
class TestEventManager extends EventManager {
|
|
addListener(callback, ...args) {
|
|
super.addListener(function(...args) {
|
|
try {
|
|
callback.call(this, ...args);
|
|
} catch (e) {
|
|
assertTrue(false, `${e}\n${e.stack}`);
|
|
}
|
|
}, ...args);
|
|
}
|
|
}
|
|
|
|
if (!Cu.isInAutomation && !isXpcshell) {
|
|
return { test: {} };
|
|
}
|
|
|
|
return {
|
|
test: {
|
|
withHandlingUserInput(callback) {
|
|
// TODO(Bug 1598804): remove this once we don't expose anymore the
|
|
// entire test API namespace based on an environment variable.
|
|
if (!Cu.isInAutomation) {
|
|
// This dangerous method should only be available if the
|
|
// automation pref is set, which is the case in browser tests.
|
|
throw new ExtensionUtils.ExtensionError(
|
|
"withHandlingUserInput can only be called in automation"
|
|
);
|
|
}
|
|
ExtensionCommon.withHandlingUserInput(
|
|
context.contentWindow,
|
|
callback
|
|
);
|
|
},
|
|
|
|
sendMessage(...args) {
|
|
extension.emit("test-message", ...args);
|
|
},
|
|
|
|
notifyPass(msg) {
|
|
extension.emit("test-done", true, msg, getStack());
|
|
},
|
|
|
|
notifyFail(msg) {
|
|
extension.emit("test-done", false, msg, getStack());
|
|
},
|
|
|
|
log(msg) {
|
|
extension.emit("test-log", true, msg, getStack());
|
|
},
|
|
|
|
fail(msg) {
|
|
assertTrue(false, msg);
|
|
},
|
|
|
|
succeed(msg) {
|
|
assertTrue(true, msg);
|
|
},
|
|
|
|
assertTrue(value, msg) {
|
|
assertTrue(value, msg);
|
|
},
|
|
|
|
assertFalse(value, msg) {
|
|
assertTrue(!value, msg);
|
|
},
|
|
|
|
assertEq(expected, actual, msg) {
|
|
let equal = expected === actual;
|
|
|
|
expected = String(expected);
|
|
actual = String(actual);
|
|
|
|
if (!equal && expected === actual) {
|
|
actual += " (different)";
|
|
}
|
|
extension.emit(
|
|
"test-eq",
|
|
equal,
|
|
String(msg),
|
|
expected,
|
|
actual,
|
|
getStack()
|
|
);
|
|
},
|
|
|
|
assertRejects(promise, expectedError, msg) {
|
|
// Wrap in a native promise for consistency.
|
|
promise = Promise.resolve(promise);
|
|
|
|
return promise.then(
|
|
result => {
|
|
assertTrue(false, `Promise resolved, expected rejection: ${msg}`);
|
|
},
|
|
error => {
|
|
let errorMessage = toSource(error && error.message);
|
|
|
|
assertTrue(
|
|
errorMatches(error, expectedError, context),
|
|
`Promise rejected, expecting rejection to match ${toSource(
|
|
expectedError
|
|
)}, got ${errorMessage}: ${msg}`
|
|
);
|
|
}
|
|
);
|
|
},
|
|
|
|
assertThrows(func, expectedError, msg) {
|
|
try {
|
|
func();
|
|
|
|
assertTrue(false, `Function did not throw, expected error: ${msg}`);
|
|
} catch (error) {
|
|
let errorMessage = toSource(error && error.message);
|
|
|
|
assertTrue(
|
|
errorMatches(error, expectedError, context),
|
|
`Function threw, expecting error to match ${toSource(
|
|
expectedError
|
|
)}, got ${errorMessage}: ${msg}`
|
|
);
|
|
}
|
|
},
|
|
|
|
onMessage: new TestEventManager({
|
|
context,
|
|
name: "test.onMessage",
|
|
register: fire => {
|
|
let handler = (event, ...args) => {
|
|
fire.async(...args);
|
|
};
|
|
|
|
extension.on("test-harness-message", handler);
|
|
return () => {
|
|
extension.off("test-harness-message", handler);
|
|
};
|
|
},
|
|
}).api(),
|
|
},
|
|
};
|
|
}
|
|
};
|