Bug 1583897 - Send a telemetry event for new sendtab. r=tcsc,eoger,lina

Differential Revision: https://phabricator.services.mozilla.com/D48153

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mark Hammond 2019-10-14 22:17:28 +00:00
parent 0b08adcfbb
commit 0481284ac7
12 changed files with 461 additions and 171 deletions

View File

@ -92,6 +92,12 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/FxAccountsProfile.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FxAccountsTelemetry",
"resource://gre/modules/FxAccountsTelemetry.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
Preferences: "resource://gre/modules/Preferences.jsm",
});
@ -409,6 +415,10 @@ class FxAccounts {
return this._internal.keys;
}
get telemetry() {
return this._internal.telemetry;
}
_withCurrentAccountState(func) {
return this._internal.withCurrentAccountState(func);
}
@ -858,6 +868,14 @@ FxAccountsInternal.prototype = {
return this._device;
},
_telemetry: null,
get telemetry() {
if (!this._telemetry) {
this._telemetry = new FxAccountsTelemetry();
}
return this._telemetry;
},
// A hook-point for tests who may want a mocked AccountState or mocked storage.
newAccountState(credentials) {
let storage = new FxAccountsStorageManager();

View File

@ -4,7 +4,7 @@
const EXPORTED_SYMBOLS = ["SendTab", "FxAccountsCommands"];
const { COMMAND_SENDTAB, log } = ChromeUtils.import(
const { COMMAND_SENDTAB, COMMAND_SENDTAB_TAIL, log } = ChromeUtils.import(
"resource://gre/modules/FxAccountsCommon.js"
);
ChromeUtils.defineModuleGetter(
@ -149,7 +149,7 @@ class FxAccountsCommands {
switch (command) {
case COMMAND_SENDTAB:
try {
const { title, uri } = await this.sendTab.handle(payload);
const { title, uri } = await this.sendTab.handle(senderId, payload);
log.info(
`Tab received with FxA commands: ${title} from ${
sender ? sender.name : "Unknown device"
@ -192,10 +192,9 @@ class SendTab {
*/
async send(to, tab) {
log.info(`Sending a tab to ${to.length} devices.`);
const flowID = this._fxai.telemetry.generateFlowID();
const encoder = new TextEncoder("utf8");
const data = {
entries: [{ title: tab.title, url: tab.url }],
};
const data = { entries: [{ title: tab.title, url: tab.url }] };
const bytes = encoder.encode(JSON.stringify(data));
const report = {
succeeded: [],
@ -204,8 +203,14 @@ class SendTab {
for (let device of to) {
try {
const encrypted = await this._encrypt(bytes, device);
const payload = { encrypted };
const payload = { encrypted, flowID };
await this._commands.invoke(COMMAND_SENDTAB, device, payload); // FxA needs an object.
this._fxai.telemetry.recordEvent(
"command-sent",
COMMAND_SENDTAB_TAIL,
this._fxai.telemetry.sanitizeDeviceId(device.id),
{ flowID }
);
report.succeeded.push(device);
} catch (error) {
log.error("Error while invoking a send tab command.", error);
@ -228,7 +233,7 @@ class SendTab {
}
// Handle incoming send tab payload, called by FxAccountsCommands.
async handle({ encrypted }) {
async handle(senderID, { encrypted, flowID }) {
const bytes = await this._decrypt(encrypted);
const decoder = new TextDecoder("utf8");
const data = JSON.parse(decoder.decode(bytes));
@ -236,6 +241,13 @@ class SendTab {
? data.current
: data.entries.length - 1;
const { title, url: uri } = data.entries[current];
this._fxai.telemetry.recordEvent(
"command-received",
COMMAND_SENDTAB_TAIL,
this._fxai.telemetry.sanitizeDeviceId(senderID),
{ flowID }
);
return {
title,
uri,

View File

@ -78,7 +78,12 @@ exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange"; // WebChann
exports.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange";
exports.ON_NEW_DEVICE_ID = "fxaccounts:new_device_id";
exports.COMMAND_SENDTAB = "https://identity.mozilla.com/cmd/open-uri";
// The common prefix for all commands.
exports.COMMAND_PREFIX = "https://identity.mozilla.com/cmd/";
// The commands we support - only the _TAIL values are recorded in telemetry.
exports.COMMAND_SENDTAB_TAIL = "open-uri";
exports.COMMAND_SENDTAB = exports.COMMAND_PREFIX + exports.COMMAND_SENDTAB_TAIL;
// OAuth
exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";

View File

@ -0,0 +1,55 @@
/* 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";
// FxA Telemetry support. For hysterical raisins, the actual implementation
// is inside "sync". We should move the core implementation somewhere that's
// sanely shared (eg, services-common?), but let's wait and see where we end up
// first...
// We use this observers module because we leverage its support for richer
// "subject" data.
const { Observers } = ChromeUtils.import(
"resource://services-common/observers.js"
);
class FxAccountsTelemetry {
recordEvent(object, method, value, extra = undefined) {
// We need to ensure the telemetry module is loaded.
ChromeUtils.import("resource://services-sync/telemetry.js");
// Now it will be listening for the notifications...
Observers.notify("fxa:telemetry:event", { object, method, value, extra });
}
// A flow ID can be anything that's "probably" unique, so for now use a UUID.
generateFlowID() {
return Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator)
.generateUUID()
.toString()
.slice(1, -1);
}
// Sanitize the ID of a device into something suitable for including in the
// ping. Returns null if no transformation is possible.
sanitizeDeviceId(deviceId) {
// We only know how to hash it for sync users, which kinda sucks.
let xps =
this._weaveXPCOM ||
Cc["@mozilla.org/weave/service;1"].getService(Ci.nsISupports)
.wrappedJSObject;
if (!xps.enabled) {
return null;
}
try {
return xps.Weave.Service.identity.hashedDeviceID(deviceId);
} catch {
// sadly this can happen in various scenarios, so don't complain.
}
return null;
}
}
var EXPORTED_SYMBOLS = ["FxAccountsTelemetry"];

View File

@ -30,6 +30,7 @@ EXTRA_JS_MODULES += [
'FxAccountsProfileClient.jsm',
'FxAccountsPush.jsm',
'FxAccountsStorage.jsm',
'FxAccountsTelemetry.jsm',
'FxAccountsWebChannel.jsm',
]

View File

@ -7,6 +7,36 @@ const { FxAccountsCommands, SendTab } = ChromeUtils.import(
"resource://gre/modules/FxAccountsCommands.js"
);
const { COMMAND_SENDTAB, COMMAND_SENDTAB_TAIL } = ChromeUtils.import(
"resource://gre/modules/FxAccountsCommon.js"
);
class TelemetryMock {
constructor() {
this._events = [];
this._uuid_counter = 0;
}
recordEvent(object, method, value, extra = undefined) {
this._events.push({ object, method, value, extra });
}
generateFlowID() {
this._uuid_counter += 1;
return this._uuid_counter.toString();
}
sanitizeDeviceId(id) {
return id + "-san";
}
}
function FxaInternalMock() {
return {
telemetry: new TelemetryMock(),
};
}
add_task(async function test_sendtab_isDeviceCompatible() {
const sendTab = new SendTab(null, null);
let device = { name: "My device" };
@ -31,14 +61,19 @@ add_task(async function test_sendtab_send() {
Assert.equal(payload.encrypted, "encryptedpayload");
}),
};
const sendTab = new SendTab(commands, null);
const fxai = FxaInternalMock();
const sendTab = new SendTab(commands, fxai);
sendTab._encrypt = (bytes, device) => {
if (device.name == "Device 2") {
throw new Error("Encrypt error!");
}
return "encryptedpayload";
};
const to = [{ name: "Device 1" }, { name: "Device 2" }, { name: "Device 3" }];
const to = [
{ name: "Device 1" },
{ name: "Device 2" },
{ id: "dev3", name: "Device 3" },
];
const tab = { title: "Foo", url: "https://foo.bar/" };
const report = await sendTab.send(to, tab);
Assert.equal(report.succeeded.length, 1);
@ -49,6 +84,62 @@ add_task(async function test_sendtab_send() {
Assert.equal(report.failed[1].device.name, "Device 2");
Assert.equal(report.failed[1].error.message, "Encrypt error!");
Assert.ok(commands.invoke.calledTwice);
Assert.deepEqual(fxai.telemetry._events, [
{
object: "command-sent",
method: COMMAND_SENDTAB_TAIL,
value: "dev3-san",
extra: { flowID: "1" },
},
]);
});
add_task(async function test_sendtab_receive() {
// We are testing 'receive' here, but might as well go through 'send'
// to package the data and for additional testing...
const commands = {
_invokes: [],
invoke(cmd, device, payload) {
this._invokes.push({ cmd, device, payload });
},
};
const fxai = FxaInternalMock();
const sendTab = new SendTab(commands, fxai);
sendTab._encrypt = (bytes, device) => {
return bytes;
};
sendTab._decrypt = bytes => {
return bytes;
};
const tab = { title: "tab title", url: "http://example.com" };
const to = [{ id: "devid", name: "The Device" }];
await sendTab.send(to, tab);
Assert.equal(commands._invokes.length, 1);
for (let { cmd, device, payload } of commands._invokes) {
Assert.equal(cmd, COMMAND_SENDTAB);
Assert.deepEqual(await sendTab.handle(device.id, payload), {
title: "tab title",
uri: "http://example.com",
});
}
Assert.deepEqual(fxai.telemetry._events, [
{
object: "command-sent",
method: COMMAND_SENDTAB_TAIL,
value: "devid-san",
extra: { flowID: "1" },
},
{
object: "command-received",
method: COMMAND_SENDTAB_TAIL,
value: "devid-san",
extra: { flowID: "1" },
},
]);
});
add_task(async function test_commands_pollDeviceCommands_push() {

View File

@ -9,45 +9,27 @@ var EXPORTED_SYMBOLS = ["SyncTelemetry"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { Log } = ChromeUtils.import("resource://gre/modules/Log.jsm");
const { AuthenticationError } = ChromeUtils.import(
"resource://services-sync/browserid_identity.js"
);
const { Weave } = ChromeUtils.import("resource://services-sync/main.js");
const { Status } = ChromeUtils.import("resource://services-sync/status.js");
const { Svc } = ChromeUtils.import("resource://services-sync/util.js");
const { Resource } = ChromeUtils.import("resource://services-sync/resource.js");
const { Observers } = ChromeUtils.import(
"resource://services-common/observers.js"
);
const { Async } = ChromeUtils.import("resource://services-common/async.js");
XPCOMUtils.defineLazyModuleGetters(this, {
Async: "resource://services-common/async.js",
AuthenticationError: "resource://services-sync/browserid_identity.js",
Log: "resource://gre/modules/Log.jsm",
ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
Observers: "resource://services-common/observers.js",
OS: "resource://gre/modules/osfile.jsm",
Resource: "resource://services-sync/resource.js",
Services: "resource://gre/modules/Services.jsm",
Status: "resource://services-sync/status.js",
Svc: "resource://services-sync/util.js",
TelemetryController: "resource://gre/modules/TelemetryController.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
TelemetryUtils: "resource://gre/modules/TelemetryUtils.jsm",
Weave: "resource://services-sync/main.js",
});
let constants = {};
ChromeUtils.import("resource://services-sync/constants.js", constants);
ChromeUtils.defineModuleGetter(
this,
"TelemetryController",
"resource://gre/modules/TelemetryController.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"TelemetryUtils",
"resource://gre/modules/TelemetryUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"TelemetryEnvironment",
"resource://gre/modules/TelemetryEnvironment.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ObjectUtils",
"resource://gre/modules/ObjectUtils.jsm"
);
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(
this,
"Telemetry",
@ -74,6 +56,8 @@ const TOPICS = [
"weave:telemetry:event",
"weave:telemetry:histogram",
// and we are now used by FxA, so a custom event for that.
"fxa:telemetry:event",
];
const PING_FORMAT_VERSION = 1;
@ -696,6 +680,20 @@ class SyncTelemetryImpl {
return false;
}
maybeSubmitForInterval() {
// We want to submit the ping every `this.submissionInterval` but only when
// there's no current sync in progress, otherwise we may end up submitting
// the sync and the events caused by it in different pings.
if (
this.current == null &&
Telemetry.msSinceProcessStart() - this.lastSubmissionTime >
this.submissionInterval
) {
this.finish("schedule");
this.lastSubmissionTime = Telemetry.msSinceProcessStart();
}
}
onSyncFinished(error) {
if (!this.current) {
log.warn("onSyncFinished but we aren't recording");
@ -724,13 +722,7 @@ class SyncTelemetryImpl {
++this.discarded;
}
this.current = null;
if (
Telemetry.msSinceProcessStart() - this.lastSubmissionTime >
this.submissionInterval
) {
this.finish("schedule");
this.lastSubmissionTime = Telemetry.msSinceProcessStart();
}
this.maybeSubmitForInterval();
}
_addHistogram(hist) {
@ -776,6 +768,7 @@ class SyncTelemetryImpl {
event.push(extra);
}
this.events.push(event);
this.maybeSubmitForInterval();
}
observe(subject, topic, data) {
@ -853,6 +846,7 @@ class SyncTelemetryImpl {
break;
case "weave:telemetry:event":
case "fxa:telemetry:event":
this._recordEvent(subject);
break;

View File

@ -390,9 +390,40 @@ async function wait_for_ping(callback, allowErrorPings, getFullPing = false) {
return record.syncs[0];
}
// Short helper for wait_for_ping
function sync_and_validate_telem(allowErrorPings, getFullPing = false) {
return wait_for_ping(() => Service.sync(), allowErrorPings, getFullPing);
// Perform a sync and validate all telemetry caused by the sync. If fnValidate
// is null, we just check the ping records success. If fnValidate is specified,
// then the sync must have recorded just a single sync, and that sync will be
// passed to the function to be checked.
async function sync_and_validate_telem(fnValidate = null) {
let numErrors = 0;
let telem = get_sync_test_telemetry();
let oldSubmit = telem.submit;
try {
telem.submit = function(record) {
// This is called via an observer, so failures here don't cause the test
// to fail :(
try {
// All pings must be valid.
assert_valid_ping(record);
if (fnValidate) {
// for historical reasons these callbacks expect a "sync" record, not
// the entire ping.
Assert.equal(record.syncs.length, 1);
fnValidate(record.syncs[0]);
} else {
// no validation function means it must be a "success" ping.
assert_success_ping(record);
}
} catch (ex) {
print("Failure in ping validation callback", ex, "\n", ex.stack);
numErrors += 1;
}
};
await Service.sync();
Assert.ok(numErrors == 0, "There were telemetry validation errors");
} finally {
telem.submit = oldSubmit;
}
}
// Used for the (many) cases where we do a 'partial' sync, where only a single

View File

@ -133,8 +133,12 @@ add_task(async function test_locally_changed_keys() {
_("HMAC error count: " + hmacErrorCount);
// Now syncing should succeed, after one HMAC error.
let ping = await wait_for_ping(() => Service.sync(), true);
equal(ping.engines.find(e => e.name == "history").incoming.applied, 5);
await sync_and_validate_telem(ping => {
Assert.equal(
ping.engines.find(e => e.name == "history").incoming.applied,
5
);
});
Assert.equal(hmacErrorCount, 1);
_(
@ -189,12 +193,13 @@ add_task(async function test_locally_changed_keys() {
Service.lastHMACEvent = 0;
_("Syncing...");
ping = await sync_and_validate_telem(true);
await sync_and_validate_telem(ping => {
Assert.equal(
ping.engines.find(e => e.name == "history").incoming.failed,
5
);
});
Assert.equal(
ping.engines.find(e => e.name == "history").incoming.failed,
5
);
_(
"Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair
);

View File

@ -63,8 +63,9 @@ add_task(async function test_401_logout() {
Service._updateCachedURLs();
_("Starting first sync.");
let ping = await sync_and_validate_telem(true);
deepEqual(ping.failureReason, { name: "httperror", code: 401 });
await sync_and_validate_telem(ping => {
deepEqual(ping.failureReason, { name: "httperror", code: 401 });
});
_("First sync done.");
await promiseErrors;
@ -89,11 +90,12 @@ add_task(async function test_credentials_changed_logout() {
await EHTestsCommon.generateCredentialsChangedFailure();
let ping = await sync_and_validate_telem(true);
equal(ping.status.sync, CREDENTIALS_CHANGED);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed",
await sync_and_validate_telem(ping => {
equal(ping.status.sync, CREDENTIALS_CHANGED);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed",
});
});
Assert.equal(Status.sync, CREDENTIALS_CHANGED);
@ -135,11 +137,12 @@ add_task(async function test_sync_non_network_error() {
await EHTestsCommon.generateCredentialsChangedFailure();
let ping = await sync_and_validate_telem(true);
equal(ping.status.sync, CREDENTIALS_CHANGED);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed",
await sync_and_validate_telem(ping => {
equal(ping.status.sync, CREDENTIALS_CHANGED);
deepEqual(ping.failureReason, {
name: "unexpectederror",
error: "Error: Aborting sync, remote setup failed",
});
});
Assert.equal(Status.sync, CREDENTIALS_CHANGED);
@ -252,11 +255,12 @@ add_task(async function test_sync_server_maintenance_error() {
Assert.equal(Status.service, STATUS_OK);
let ping = await sync_and_validate_telem(true);
equal(ping.status.sync, SERVER_MAINTENANCE);
deepEqual(ping.engines.find(e => e.failureReason).failureReason, {
name: "httperror",
code: 503,
await sync_and_validate_telem(ping => {
equal(ping.status.sync, SERVER_MAINTENANCE);
deepEqual(ping.engines.find(e => e.failureReason).failureReason, {
name: "httperror",
code: 503,
});
});
Assert.equal(Status.service, SYNC_FAILED_PARTIAL);

View File

@ -103,7 +103,7 @@ add_task(async function test_lastSync_not_updated_on_complete_failure() {
// Do an initial sync that we expect to be successful.
let promiseObserved = promiseOneObserver("weave:service:reset-file-log");
await sync_and_validate_telem(false);
await sync_and_validate_telem();
await promiseObserved;
Assert.equal(Status.service, STATUS_OK);
@ -120,7 +120,7 @@ add_task(async function test_lastSync_not_updated_on_complete_failure() {
);
promiseObserved = promiseOneObserver("weave:service:reset-file-log");
await sync_and_validate_telem(true);
await sync_and_validate_telem(() => {});
await promiseObserved;
Assert.equal(Status.sync, SERVER_MAINTENANCE);
@ -419,9 +419,10 @@ add_task(async function test_sync_engine_generic_fail() {
});
Assert.ok(await EHTestsCommon.setUp(server));
let ping = await sync_and_validate_telem(true);
deepEqual(ping.status.service, SYNC_FAILED_PARTIAL);
deepEqual(ping.engines.find(e => e.status).status, ENGINE_UNKNOWN_FAIL);
await sync_and_validate_telem(ping => {
deepEqual(ping.status.service, SYNC_FAILED_PARTIAL);
deepEqual(ping.engines.find(e => e.status).status, ENGINE_UNKNOWN_FAIL);
});
await promiseObserved;

View File

@ -132,7 +132,7 @@ add_task(async function test_basic() {
let server = httpd_setup(handlers);
await configureIdentity({ username: "johndoe" }, server);
let ping = await sync_and_validate_telem(true, true);
let ping = await wait_for_ping(() => Service.sync(), true, true);
// Check the "os" block - we can't really check specific values, but can
// check it smells sane.
@ -426,11 +426,12 @@ add_task(async function test_generic_engine_fail() {
changes
)}`
);
let ping = await sync_and_validate_telem(true);
equal(ping.status.service, SYNC_FAILED_PARTIAL);
deepEqual(ping.engines.find(err => err.name === "steam").failureReason, {
name: "unexpectederror",
error: String(e),
await sync_and_validate_telem(ping => {
equal(ping.status.service, SYNC_FAILED_PARTIAL);
deepEqual(ping.engines.find(err => err.name === "steam").failureReason, {
name: "unexpectederror",
error: String(e),
});
});
} finally {
await cleanAndGo(engine, server);
@ -448,18 +449,20 @@ add_task(async function test_engine_fail_weird_errors() {
try {
let msg = "Bad things happened!";
engine._errToThrow = { message: msg };
let ping = await sync_and_validate_telem(true);
equal(ping.status.service, SYNC_FAILED_PARTIAL);
deepEqual(ping.engines.find(err => err.name === "steam").failureReason, {
name: "unexpectederror",
error: "Bad things happened!",
await sync_and_validate_telem(ping => {
equal(ping.status.service, SYNC_FAILED_PARTIAL);
deepEqual(ping.engines.find(err => err.name === "steam").failureReason, {
name: "unexpectederror",
error: "Bad things happened!",
});
});
let e = { msg };
engine._errToThrow = e;
ping = await sync_and_validate_telem(true);
deepEqual(ping.engines.find(err => err.name === "steam").failureReason, {
name: "unexpectederror",
error: JSON.stringify(e),
await sync_and_validate_telem(ping => {
deepEqual(ping.engines.find(err => err.name === "steam").failureReason, {
name: "unexpectederror",
error: JSON.stringify(e),
});
});
} finally {
await cleanAndGo(engine, server);
@ -485,31 +488,32 @@ add_task(async function test_overrideTelemetryName() {
try {
info("Sync with validation problems");
engine.problemsToReport = problemsToReport;
let ping = await sync_and_validate_telem(true);
let enginePing = ping.engines.find(e => e.name === "steam-but-better");
ok(enginePing);
ok(!ping.engines.find(e => e.name === "steam"));
deepEqual(
enginePing.validation,
{
version: 1,
checked: 0,
problems: problemsToReport,
},
"Should include validation report with overridden name"
);
await sync_and_validate_telem(ping => {
let enginePing = ping.engines.find(e => e.name === "steam-but-better");
ok(enginePing);
ok(!ping.engines.find(e => e.name === "steam"));
deepEqual(
enginePing.validation,
{
version: 1,
checked: 0,
problems: problemsToReport,
},
"Should include validation report with overridden name"
);
});
info("Sync without validation problems");
engine.problemsToReport = null;
ping = await sync_and_validate_telem(true);
enginePing = ping.engines.find(e => e.name === "steam-but-better");
ok(enginePing);
ok(!ping.engines.find(e => e.name === "steam"));
ok(
!enginePing.validation,
"Should not include validation report when there are no problems"
);
await sync_and_validate_telem(ping => {
let enginePing = ping.engines.find(e => e.name === "steam-but-better");
ok(enginePing);
ok(!ping.engines.find(e => e.name === "steam"));
ok(
!enginePing.validation,
"Should not include validation report when there are no problems"
);
});
} finally {
await cleanAndGo(engine, server);
await Service.engineManager.unregister(engine);
@ -542,17 +546,18 @@ add_task(async function test_engine_fail_ioerror() {
changes
)}`
);
let ping = await sync_and_validate_telem(true);
equal(ping.status.service, SYNC_FAILED_PARTIAL);
let failureReason = ping.engines.find(e => e.name === "steam")
.failureReason;
equal(failureReason.name, "unexpectederror");
// ensure the profile dir in the exception message has been stripped.
ok(
!failureReason.error.includes(OS.Constants.Path.profileDir),
failureReason.error
);
ok(failureReason.error.includes("[profileDir]"), failureReason.error);
await sync_and_validate_telem(ping => {
equal(ping.status.service, SYNC_FAILED_PARTIAL);
let failureReason = ping.engines.find(e => e.name === "steam")
.failureReason;
equal(failureReason.name, "unexpectederror");
// ensure the profile dir in the exception message has been stripped.
ok(
!failureReason.error.includes(OS.Constants.Path.profileDir),
failureReason.error
);
ok(failureReason.error.includes("[profileDir]"), failureReason.error);
});
} finally {
await cleanAndGo(engine, server);
await Service.engineManager.unregister(engine);
@ -574,23 +579,26 @@ add_task(async function test_clean_urls() {
try {
const changes = await engine._tracker.getChangedIDs();
_(`test_clean_urls: Steam tracker contents: ${JSON.stringify(changes)}`);
let ping = await sync_and_validate_telem(true);
equal(ping.status.service, SYNC_FAILED_PARTIAL);
let failureReason = ping.engines.find(e => e.name === "steam")
.failureReason;
equal(failureReason.name, "unexpectederror");
equal(failureReason.error, "<URL> is not a valid URL.");
await sync_and_validate_telem(ping => {
equal(ping.status.service, SYNC_FAILED_PARTIAL);
let failureReason = ping.engines.find(e => e.name === "steam")
.failureReason;
equal(failureReason.name, "unexpectederror");
equal(failureReason.error, "<URL> is not a valid URL.");
});
// Handle other errors that include urls.
engine._errToThrow =
"Other error message that includes some:url/foo/bar/ in it.";
ping = await sync_and_validate_telem(true);
equal(ping.status.service, SYNC_FAILED_PARTIAL);
failureReason = ping.engines.find(e => e.name === "steam").failureReason;
equal(failureReason.name, "unexpectederror");
equal(
failureReason.error,
"Other error message that includes <URL> in it."
);
await sync_and_validate_telem(ping => {
equal(ping.status.service, SYNC_FAILED_PARTIAL);
let failureReason = ping.engines.find(e => e.name === "steam")
.failureReason;
equal(failureReason.name, "unexpectederror");
equal(
failureReason.error,
"Other error message that includes <URL> in it."
);
});
} finally {
await cleanAndGo(engine, server);
await Service.engineManager.unregister(engine);
@ -659,15 +667,16 @@ add_task(async function test_nserror() {
try {
const changes = await engine._tracker.getChangedIDs();
_(`test_nserror: Steam tracker contents: ${JSON.stringify(changes)}`);
let ping = await sync_and_validate_telem(true);
deepEqual(ping.status, {
service: SYNC_FAILED_PARTIAL,
sync: LOGIN_FAILED_NETWORK_ERROR,
});
let enginePing = ping.engines.find(e => e.name === "steam");
deepEqual(enginePing.failureReason, {
name: "nserror",
code: Cr.NS_ERROR_UNKNOWN_HOST,
await sync_and_validate_telem(ping => {
deepEqual(ping.status, {
service: SYNC_FAILED_PARTIAL,
sync: LOGIN_FAILED_NETWORK_ERROR,
});
let enginePing = ping.engines.find(e => e.name === "steam");
deepEqual(enginePing.failureReason, {
name: "nserror",
code: Cr.NS_ERROR_UNKNOWN_HOST,
});
});
} finally {
await cleanAndGo(engine, server);
@ -757,7 +766,7 @@ add_task(async function test_discarding() {
}
telem.submit = oldSubmit;
telem.submissionInterval = -1;
let ping = await sync_and_validate_telem(true, true); // with this we've synced 6 times
let ping = await wait_for_ping(() => Service.sync(), true, true); // with this we've synced 6 times
equal(ping.syncs.length, 2);
equal(ping.discarded, 4);
} finally {
@ -770,6 +779,39 @@ add_task(async function test_discarding() {
}
});
add_task(async function test_submit_interval() {
let telem = get_sync_test_telemetry();
let oldSubmit = telem.submit;
let numSubmissions = 0;
telem.submit = function() {
numSubmissions += 1;
};
function notify(what, data = null) {
Svc.Obs.notify(what, JSON.stringify(data));
}
try {
// submissionInterval is set such that each sync should submit
notify("weave:service:sync:start", { why: "testing" });
notify("weave:service:sync:finish");
Assert.equal(numSubmissions, 1, "should submit this ping due to interval");
// As should each event outside of a sync.
Service.recordTelemetryEvent("object", "method");
Assert.equal(numSubmissions, 2);
// But events while we are syncing should not.
notify("weave:service:sync:start", { why: "testing" });
Service.recordTelemetryEvent("object", "method");
Assert.equal(numSubmissions, 2, "no submission for this event");
notify("weave:service:sync:finish");
Assert.equal(numSubmissions, 3, "was submitted after sync finish");
} finally {
telem.submit = oldSubmit;
}
});
add_task(async function test_no_foreign_engines_in_error_ping() {
enableValidationPrefs();
@ -780,9 +822,10 @@ add_task(async function test_no_foreign_engines_in_error_ping() {
engine._errToThrow = new Error("Oh no!");
await SyncTestingInfrastructure(server);
try {
let ping = await sync_and_validate_telem(true);
equal(ping.status.service, SYNC_FAILED_PARTIAL);
ok(ping.engines.every(e => e.name !== "bogus"));
await sync_and_validate_telem(ping => {
equal(ping.status.service, SYNC_FAILED_PARTIAL);
ok(ping.engines.every(e => e.name !== "bogus"));
});
} finally {
await cleanAndGo(engine, server);
await Service.engineManager.unregister(engine);
@ -799,8 +842,9 @@ add_task(async function test_no_foreign_engines_in_success_ping() {
await SyncTestingInfrastructure(server);
try {
let ping = await sync_and_validate_telem();
ok(ping.engines.every(e => e.name !== "bogus"));
await sync_and_validate_telem(ping => {
ok(ping.engines.every(e => e.name !== "bogus"));
});
} finally {
await cleanAndGo(engine, server);
await Service.engineManager.unregister(engine);
@ -816,6 +860,10 @@ add_task(async function test_events() {
let server = await serverForFoo(engine);
await SyncTestingInfrastructure(server);
let telem = get_sync_test_telemetry();
telem.submissionInterval = Infinity;
try {
let serverTime = Resource.serverTime;
Service.recordTelemetryEvent("object", "method", "value", { foo: "bar" });
@ -828,28 +876,53 @@ add_task(async function test_events() {
equal(object, "object");
equal(value, "value");
deepEqual(extra, { foo: "bar", serverTime: String(serverTime) });
// Test with optional values.
Service.recordTelemetryEvent("object", "method");
ping = await wait_for_ping(() => Service.sync(), false, true);
ping = await wait_for_ping(
() => {
// Test with optional values.
Service.recordTelemetryEvent("object", "method");
},
false,
true
);
equal(ping.events.length, 1);
equal(ping.events[0].length, 4);
Service.recordTelemetryEvent("object", "method", "extra");
ping = await wait_for_ping(() => Service.sync(), false, true);
ping = await wait_for_ping(
() => {
Service.recordTelemetryEvent("object", "method", "extra");
},
false,
true
);
equal(ping.events.length, 1);
equal(ping.events[0].length, 5);
Service.recordTelemetryEvent("object", "method", undefined, { foo: "bar" });
ping = await wait_for_ping(() => Service.sync(), false, true);
ping = await wait_for_ping(
() => {
Service.recordTelemetryEvent("object", "method", undefined, {
foo: "bar",
});
},
false,
true
);
equal(ping.events.length, 1);
equal(ping.events[0].length, 6);
[timestamp, category, method, object, value, extra] = ping.events[0];
equal(value, null);
Service.recordTelemetryEvent("object", "method", undefined, { foo: "bar" });
let telem = get_sync_test_telemetry();
// Fake a submission due to shutdown.
ping = await wait_for_ping(() => telem.finish("shutdown"), false, true);
ping = await wait_for_ping(
() => {
telem.submissionInterval = Infinity;
Service.recordTelemetryEvent("object", "method", undefined, {
foo: "bar",
});
telem.finish("shutdown");
},
false,
true
);
equal(ping.syncs.length, 0);
equal(ping.events.length, 1);
equal(ping.events[0].length, 6);