mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-04 18:29:29 +02:00
If ASRouter._recordReachEvent throws an error, right now we don't handle it, so we don't send exposure, and we don't route the message either. This patch makes that method catch all errors that occur inside it, much like the Glean record method. The test we include forces an exception by replacing Glean's reach message with a stub that throws, even though it doesn't seem particularly likely that this is what's happening in the field. What's happening in the field seems to happen on the FxMS messaging IDs that are configured incorrectly AND that have some other as-yet-unknown property. There are a couple of live experiments that have (have had) issues but ONLY AFTER A CERTAIN POINT (maybe related to 138 hitting release). The fix for https://bugzilla.mozilla.org/show_bug.cgi?id=1965869 has repaired the configuration on the recent misconfigured feature ids, which will likely fix many/most problems. This adds another bandaid, where if there's some issue inside _recordReachEvent, we'll handle that too. More could be done to bulletproof sendTriggerMessage, where the relevant code all lives (we could consider putting the entire thing inside a try/catch block, and ideally even send telemetry if the catch block gets hit). I'm open to thoughts about that... Differential Revision: https://phabricator.services.mozilla.com/D250302
296 lines
7.8 KiB
JavaScript
296 lines
7.8 KiB
JavaScript
/**
|
|
* GlobalOverrider - Utility that allows you to override properties on the global object.
|
|
* See unit-entry.js for example usage.
|
|
*/
|
|
export class GlobalOverrider {
|
|
constructor() {
|
|
this.originalGlobals = new Map();
|
|
this.sandbox = sinon.createSandbox();
|
|
}
|
|
|
|
/**
|
|
* _override - Internal method to override properties on the global object.
|
|
* The first time a given key is overridden, we cache the original
|
|
* value in this.originalGlobals so that later it can be restored.
|
|
*
|
|
* @param {string} key The identifier of the property
|
|
* @param {any} value The value to which the property should be reassigned
|
|
*/
|
|
_override(key, value) {
|
|
if (!this.originalGlobals.has(key)) {
|
|
this.originalGlobals.set(key, global[key]);
|
|
}
|
|
global[key] = value;
|
|
}
|
|
|
|
/**
|
|
* set - Override a given property, or all properties on an object
|
|
*
|
|
* @param {string|object} key If a string, the identifier of the property
|
|
* If an object, a number of properties and values to which they should be reassigned.
|
|
* @param {any} value The value to which the property should be reassigned
|
|
* @return {type} description
|
|
*/
|
|
set(key, value) {
|
|
if (!value && typeof key === "object") {
|
|
const overrides = key;
|
|
Object.keys(overrides).forEach(k => this._override(k, overrides[k]));
|
|
} else {
|
|
this._override(key, value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* reset - Reset the global sandbox, so all state on spies, stubs etc. is cleared.
|
|
* You probably want to call this after each test.
|
|
*/
|
|
reset() {
|
|
this.sandbox.reset();
|
|
}
|
|
|
|
/**
|
|
* restore - Restore the global sandbox and reset all overriden properties to
|
|
* their original values. You should call this after all tests have completed.
|
|
*/
|
|
restore() {
|
|
this.sandbox.restore();
|
|
this.originalGlobals.forEach((value, key) => {
|
|
global[key] = value;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A map of mocked preference names and values, used by `FakensIPrefBranch`,
|
|
* `FakensIPrefService`, and `FakePrefs`.
|
|
*
|
|
* Tests should add entries to this map for any preferences they'd like to set,
|
|
* and remove any entries during teardown for preferences that shouldn't be
|
|
* shared between tests.
|
|
*/
|
|
export const FAKE_GLOBAL_PREFS = new Map();
|
|
|
|
/**
|
|
* Very simple fake for the most basic semantics of nsIPrefBranch. Lots of
|
|
* things aren't yet supported. Feel free to add them in.
|
|
*
|
|
* @param {Object} args - optional arguments
|
|
* @param {Function} args.initHook - if present, will be called back
|
|
* inside the constructor. Typically used from tests
|
|
* to save off a pointer to the created instance so that
|
|
* stubs and spies can be inspected by the test code.
|
|
*/
|
|
export class FakensIPrefBranch {
|
|
PREF_INVALID = "invalid";
|
|
PREF_INT = "integer";
|
|
PREF_BOOL = "boolean";
|
|
PREF_STRING = "string";
|
|
|
|
constructor(args) {
|
|
if (args) {
|
|
if ("initHook" in args) {
|
|
args.initHook.call(this);
|
|
}
|
|
if (args.defaultBranch) {
|
|
this.prefs = new Map();
|
|
} else {
|
|
this.prefs = FAKE_GLOBAL_PREFS;
|
|
}
|
|
} else {
|
|
this.prefs = FAKE_GLOBAL_PREFS;
|
|
}
|
|
this._prefBranch = {};
|
|
this.observers = new Map();
|
|
}
|
|
addObserver(prefix, callback) {
|
|
this.observers.set(prefix, callback);
|
|
}
|
|
removeObserver(prefix, callback) {
|
|
this.observers.delete(prefix, callback);
|
|
}
|
|
setStringPref(prefName, value) {
|
|
this.set(prefName, value);
|
|
}
|
|
getStringPref(prefName, defaultValue) {
|
|
return this.get(prefName, defaultValue);
|
|
}
|
|
setBoolPref(prefName, value) {
|
|
this.set(prefName, value);
|
|
}
|
|
getBoolPref(prefName) {
|
|
return this.get(prefName);
|
|
}
|
|
setIntPref(prefName, value) {
|
|
this.set(prefName, value);
|
|
}
|
|
getIntPref(prefName) {
|
|
return this.get(prefName);
|
|
}
|
|
setCharPref(prefName, value) {
|
|
this.set(prefName, value);
|
|
}
|
|
getCharPref(prefName) {
|
|
return this.get(prefName);
|
|
}
|
|
clearUserPref(prefName) {
|
|
this.prefs.delete(prefName);
|
|
}
|
|
get(prefName, defaultValue) {
|
|
let value = this.prefs.get(prefName);
|
|
return typeof value === "undefined" ? defaultValue : value;
|
|
}
|
|
getPrefType(prefName) {
|
|
let value = this.prefs.get(prefName);
|
|
switch (typeof value) {
|
|
case "number":
|
|
return this.PREF_INT;
|
|
|
|
case "boolean":
|
|
return this.PREF_BOOL;
|
|
|
|
case "string":
|
|
return this.PREF_STRING;
|
|
|
|
default:
|
|
return this.PREF_INVALID;
|
|
}
|
|
}
|
|
set(prefName, value) {
|
|
this.prefs.set(prefName, value);
|
|
|
|
// Trigger all observers for prefixes of the changed pref name. This matches
|
|
// the semantics of `nsIPrefBranch`.
|
|
let observerPrefixes = [...this.observers.keys()].filter(prefix =>
|
|
prefName.startsWith(prefix)
|
|
);
|
|
for (let observerPrefix of observerPrefixes) {
|
|
this.observers.get(observerPrefix)("", "", prefName);
|
|
}
|
|
}
|
|
getChildList(prefix) {
|
|
return [...this.prefs.keys()].filter(prefName =>
|
|
prefName.startsWith(prefix)
|
|
);
|
|
}
|
|
prefHasUserValue(prefName) {
|
|
return this.prefs.has(prefName);
|
|
}
|
|
prefIsLocked(_prefName) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A fake `Services.prefs` implementation that extends `FakensIPrefBranch`
|
|
* with methods specific to `nsIPrefService`.
|
|
*/
|
|
export class FakensIPrefService extends FakensIPrefBranch {
|
|
getBranch() {}
|
|
getDefaultBranch(_prefix) {
|
|
return {
|
|
setBoolPref() {},
|
|
setIntPref() {},
|
|
setStringPref() {},
|
|
clearUserPref() {},
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Very simple fake for the most basic semantics of Preferences.sys.mjs.
|
|
* Extends FakensIPrefBranch.
|
|
*/
|
|
export class FakePrefs extends FakensIPrefBranch {
|
|
observe(prefName, callback) {
|
|
super.addObserver(prefName, callback);
|
|
}
|
|
ignore(prefName, callback) {
|
|
super.removeObserver(prefName, callback);
|
|
}
|
|
observeBranch(_listener) {}
|
|
ignoreBranch(_listener) {}
|
|
set(prefName, value) {
|
|
this.prefs.set(prefName, value);
|
|
|
|
// Trigger observers for just the changed pref name, not any of its
|
|
// prefixes. This matches the semantics of `Preferences.sys.mjs`.
|
|
if (this.observers.has(prefName)) {
|
|
this.observers.get(prefName)(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class FakeConsoleAPI {
|
|
static LOG_LEVELS = {
|
|
all: Number.MIN_VALUE,
|
|
debug: 2,
|
|
log: 3,
|
|
info: 3,
|
|
clear: 3,
|
|
trace: 3,
|
|
timeEnd: 3,
|
|
time: 3,
|
|
assert: 3,
|
|
group: 3,
|
|
groupEnd: 3,
|
|
profile: 3,
|
|
profileEnd: 3,
|
|
dir: 3,
|
|
dirxml: 3,
|
|
warn: 4,
|
|
error: 5,
|
|
off: Number.MAX_VALUE,
|
|
};
|
|
|
|
constructor({ prefix = "", maxLogLevel = "all" } = {}) {
|
|
this.prefix = prefix;
|
|
this.prefixStr = prefix ? `${prefix}: ` : "";
|
|
this.maxLogLevel = maxLogLevel;
|
|
|
|
for (const level of Object.keys(FakeConsoleAPI.LOG_LEVELS)) {
|
|
// eslint-disable-next-line no-console
|
|
if (typeof console[level] === "function") {
|
|
this[level] = this.shouldLog(level)
|
|
? this._log.bind(this, level)
|
|
: () => {};
|
|
}
|
|
}
|
|
}
|
|
shouldLog(level) {
|
|
return (
|
|
FakeConsoleAPI.LOG_LEVELS[this.maxLogLevel] <=
|
|
FakeConsoleAPI.LOG_LEVELS[level]
|
|
);
|
|
}
|
|
_log(level, ...args) {
|
|
console[level](this.prefixStr, ...args); // eslint-disable-line no-console
|
|
}
|
|
}
|
|
|
|
export function FakeNimbusFeature() {
|
|
return {
|
|
getEnrollmentMetadata() {},
|
|
getVariable() {},
|
|
getAllVariables() {},
|
|
onUpdate() {},
|
|
offUpdate() {},
|
|
recordExposureEvent() {},
|
|
};
|
|
}
|
|
|
|
export function FakeNimbusFeatures(featureIds) {
|
|
return Object.fromEntries(
|
|
featureIds.map(featureId => [featureId, FakeNimbusFeature()])
|
|
);
|
|
}
|
|
|
|
export class FakeLogger extends FakeConsoleAPI {
|
|
constructor() {
|
|
super({
|
|
// Don't use a prefix because the first instance gets cached and reused by
|
|
// other consumers that would otherwise pass their own identifying prefix.
|
|
maxLogLevel: "off", // Change this to "debug" or "all" to get more logging in tests
|
|
});
|
|
}
|
|
}
|