forked from mirrors/gecko-dev
		
	Differential Revision: https://phabricator.services.mozilla.com/D66128 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			403 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Any copyright is dedicated to the Public Domain.
 | 
						|
 http://creativecommons.org/publicdomain/zero/1.0/
 | 
						|
 */
 | 
						|
 | 
						|
// This tests the public Telemetry API for submitting Health pings.
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/TelemetryStorage.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
 | 
						|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
 | 
						|
ChromeUtils.import(
 | 
						|
  "resource://testing-common/TelemetryArchiveTesting.jsm",
 | 
						|
  this
 | 
						|
);
 | 
						|
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "TelemetryHealthPing",
 | 
						|
  "resource://gre/modules/HealthPing.jsm"
 | 
						|
);
 | 
						|
 | 
						|
function checkHealthPingStructure(ping, expectedFailuresDict) {
 | 
						|
  let payload = ping.payload;
 | 
						|
  Assert.equal(
 | 
						|
    ping.type,
 | 
						|
    TelemetryHealthPing.HEALTH_PING_TYPE,
 | 
						|
    "Should have recorded a health ping."
 | 
						|
  );
 | 
						|
 | 
						|
  for (let [key, value] of Object.entries(expectedFailuresDict)) {
 | 
						|
    Assert.deepEqual(
 | 
						|
      payload[key],
 | 
						|
      value,
 | 
						|
      "Should have recorded correct entry with key: " + key
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function fakeHealthSchedulerTimer(set, clear) {
 | 
						|
  let telemetryHealthPing = ChromeUtils.import(
 | 
						|
    "resource://gre/modules/HealthPing.jsm",
 | 
						|
    null
 | 
						|
  );
 | 
						|
  telemetryHealthPing.Policy.setSchedulerTickTimeout = set;
 | 
						|
  telemetryHealthPing.Policy.clearSchedulerTickTimeout = clear;
 | 
						|
}
 | 
						|
 | 
						|
async function waitForConditionWithPromise(
 | 
						|
  promiseFn,
 | 
						|
  timeoutMsg,
 | 
						|
  tryCount = 30
 | 
						|
) {
 | 
						|
  const SINGLE_TRY_TIMEOUT = 100;
 | 
						|
  let tries = 0;
 | 
						|
  do {
 | 
						|
    try {
 | 
						|
      return await promiseFn();
 | 
						|
    } catch (ex) {}
 | 
						|
    await new Promise(resolve => do_timeout(SINGLE_TRY_TIMEOUT, resolve));
 | 
						|
  } while (++tries <= tryCount);
 | 
						|
  throw new Error(timeoutMsg);
 | 
						|
}
 | 
						|
 | 
						|
function fakeSendSubmissionTimeout(timeOut) {
 | 
						|
  let telemetryHealthPing = ChromeUtils.import(
 | 
						|
    "resource://gre/modules/TelemetrySend.jsm",
 | 
						|
    null
 | 
						|
  );
 | 
						|
  telemetryHealthPing.Policy.pingSubmissionTimeout = () => timeOut;
 | 
						|
}
 | 
						|
 | 
						|
add_task(async function setup() {
 | 
						|
  // Trigger a proper telemetry init.
 | 
						|
  do_get_profile(true);
 | 
						|
  // Make sure we don't generate unexpected pings due to pref changes.
 | 
						|
  await setEmptyPrefWatchlist();
 | 
						|
  Preferences.set(TelemetryUtils.Preferences.HealthPingEnabled, true);
 | 
						|
 | 
						|
  await TelemetryController.testSetup();
 | 
						|
  PingServer.start();
 | 
						|
  TelemetrySend.setServer("http://localhost:" + PingServer.port);
 | 
						|
  Preferences.set(
 | 
						|
    TelemetryUtils.Preferences.Server,
 | 
						|
    "http://localhost:" + PingServer.port
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_sendImmediately() {
 | 
						|
  PingServer.clearRequests();
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
 | 
						|
  await TelemetryHealthPing.recordSendFailure("testProblem");
 | 
						|
  let ping = await PingServer.promiseNextPing();
 | 
						|
  checkHealthPingStructure(ping, {
 | 
						|
    [TelemetryHealthPing.FailureType.SEND_FAILURE]: {
 | 
						|
      testProblem: 1,
 | 
						|
    },
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.IMMEDIATE,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_sendOnDelay() {
 | 
						|
  PingServer.clearRequests();
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
 | 
						|
  // This first failure should immediately trigger a ping. After this, subsequent failures should be throttled.
 | 
						|
  await TelemetryHealthPing.recordSendFailure("testFailure");
 | 
						|
  let testPing = await PingServer.promiseNextPing();
 | 
						|
  Assert.equal(
 | 
						|
    testPing.type,
 | 
						|
    TelemetryHealthPing.HEALTH_PING_TYPE,
 | 
						|
    "Should have recorded a health ping."
 | 
						|
  );
 | 
						|
 | 
						|
  // Retrieve delayed call back.
 | 
						|
  let pingSubmissionCallBack = null;
 | 
						|
  fakeHealthSchedulerTimer(
 | 
						|
    callBack => (pingSubmissionCallBack = callBack),
 | 
						|
    () => {}
 | 
						|
  );
 | 
						|
 | 
						|
  // Record two failures, health ping must not be send now.
 | 
						|
  await TelemetryHealthPing.recordSendFailure("testFailure");
 | 
						|
  await TelemetryHealthPing.recordSendFailure("testFailure");
 | 
						|
 | 
						|
  // Wait for sending delayed health ping.
 | 
						|
  await pingSubmissionCallBack();
 | 
						|
 | 
						|
  let ping = await PingServer.promiseNextPing();
 | 
						|
  checkHealthPingStructure(ping, {
 | 
						|
    [TelemetryHealthPing.FailureType.SEND_FAILURE]: {
 | 
						|
      testFailure: 2,
 | 
						|
    },
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.DELAYED,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_sendOverSizedPing() {
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
  PingServer.clearRequests();
 | 
						|
  let OVER_SIZED_PING_TYPE = "over-sized-ping";
 | 
						|
  let overSizedData = generateRandomString(2 * 1024 * 1024);
 | 
						|
 | 
						|
  await TelemetryController.submitExternalPing(OVER_SIZED_PING_TYPE, {
 | 
						|
    data: overSizedData,
 | 
						|
  });
 | 
						|
  let ping = await PingServer.promiseNextPing();
 | 
						|
 | 
						|
  checkHealthPingStructure(ping, {
 | 
						|
    [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
 | 
						|
      [OVER_SIZED_PING_TYPE]: 1,
 | 
						|
    },
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.IMMEDIATE,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_healthPingOnTop() {
 | 
						|
  PingServer.clearRequests();
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
 | 
						|
  let PING_TYPE = "priority-ping";
 | 
						|
 | 
						|
  // Fake now to be in throttled state.
 | 
						|
  let now = fakeNow(2050, 1, 2, 0, 0, 0);
 | 
						|
  fakeMidnightPingFuzzingDelay(60 * 1000);
 | 
						|
 | 
						|
  for (let value of [PING_TYPE, PING_TYPE, "health", PING_TYPE]) {
 | 
						|
    TelemetryController.submitExternalPing(value, {});
 | 
						|
  }
 | 
						|
 | 
						|
  // Now trigger sending pings again.
 | 
						|
  fakeNow(futureDate(now, 5 * 60 * 1000));
 | 
						|
  await TelemetrySend.notifyCanUpload();
 | 
						|
  let scheduler = ChromeUtils.import(
 | 
						|
    "resource://gre/modules/TelemetrySend.jsm",
 | 
						|
    null
 | 
						|
  );
 | 
						|
  scheduler.SendScheduler.triggerSendingPings(true);
 | 
						|
 | 
						|
  let pings = await PingServer.promiseNextPings(4);
 | 
						|
  Assert.equal(
 | 
						|
    pings[0].type,
 | 
						|
    "health",
 | 
						|
    "Should have received the health ping first."
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_sendOnTimeout() {
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
  await TelemetrySend.reset();
 | 
						|
  PingServer.clearRequests();
 | 
						|
  let PING_TYPE = "ping-on-timeout";
 | 
						|
 | 
						|
  // Disable send retry to make this test more deterministic.
 | 
						|
  fakePingSendTimer(
 | 
						|
    () => {},
 | 
						|
    () => {}
 | 
						|
  );
 | 
						|
 | 
						|
  // Set up small ping submission timeout to always have timeout error.
 | 
						|
  fakeSendSubmissionTimeout(2);
 | 
						|
 | 
						|
  await TelemetryController.submitExternalPing(PING_TYPE, {});
 | 
						|
 | 
						|
  let response;
 | 
						|
  PingServer.registerPingHandler((req, res) => {
 | 
						|
    PingServer.resetPingHandler();
 | 
						|
    // We don't finish the response yet to make sure to trigger a timeout.
 | 
						|
    res.processAsync();
 | 
						|
    response = res;
 | 
						|
  });
 | 
						|
 | 
						|
  // Wait for health ping.
 | 
						|
  let ac = new TelemetryArchiveTesting.Checker();
 | 
						|
  await ac.promiseInit();
 | 
						|
  await waitForConditionWithPromise(() => {
 | 
						|
    ac.promiseFindPing("health", []);
 | 
						|
  }, "Failed to find health ping");
 | 
						|
 | 
						|
  if (response) {
 | 
						|
    response.finish();
 | 
						|
  }
 | 
						|
 | 
						|
  let telemetryHealthPing = ChromeUtils.import(
 | 
						|
    "resource://gre/modules/TelemetrySend.jsm",
 | 
						|
    null
 | 
						|
  );
 | 
						|
  fakeSendSubmissionTimeout(telemetryHealthPing.PING_SUBMIT_TIMEOUT_MS);
 | 
						|
  PingServer.resetPingHandler();
 | 
						|
  TelemetrySend.notifyCanUpload();
 | 
						|
 | 
						|
  let pings = await PingServer.promiseNextPings(2);
 | 
						|
  let healthPing = pings.find(ping => ping.type === "health");
 | 
						|
  checkHealthPingStructure(healthPing, {
 | 
						|
    [TelemetryHealthPing.FailureType.SEND_FAILURE]: {
 | 
						|
      timeout: 1,
 | 
						|
    },
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.IMMEDIATE,
 | 
						|
  });
 | 
						|
  await TelemetryStorage.testClearPendingPings();
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_sendOnlyTopTenDiscardedPings() {
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
  await TelemetrySend.reset();
 | 
						|
  PingServer.clearRequests();
 | 
						|
  let PING_TYPE = "sort-discarded";
 | 
						|
 | 
						|
  // This first failure should immediately trigger a ping. After this, subsequent failures should be throttled.
 | 
						|
  await TelemetryHealthPing.recordSendFailure("testFailure");
 | 
						|
  let testPing = await PingServer.promiseNextPing();
 | 
						|
  Assert.equal(
 | 
						|
    testPing.type,
 | 
						|
    TelemetryHealthPing.HEALTH_PING_TYPE,
 | 
						|
    "Should have recorded a health ping."
 | 
						|
  );
 | 
						|
 | 
						|
  // Retrieve delayed call back.
 | 
						|
  let pingSubmissionCallBack = null;
 | 
						|
  fakeHealthSchedulerTimer(
 | 
						|
    callBack => (pingSubmissionCallBack = callBack),
 | 
						|
    () => {}
 | 
						|
  );
 | 
						|
 | 
						|
  // Add failures
 | 
						|
  for (let i = 1; i < 12; i++) {
 | 
						|
    for (let j = 1; j < i; j++) {
 | 
						|
      TelemetryHealthPing.recordDiscardedPing(PING_TYPE + i);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  await TelemetrySend.reset();
 | 
						|
  await pingSubmissionCallBack();
 | 
						|
  let ping = await PingServer.promiseNextPing();
 | 
						|
 | 
						|
  checkHealthPingStructure(ping, {
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.DELAYED,
 | 
						|
    [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
 | 
						|
      [PING_TYPE + 11]: 10,
 | 
						|
      [PING_TYPE + 10]: 9,
 | 
						|
      [PING_TYPE + 9]: 8,
 | 
						|
      [PING_TYPE + 8]: 7,
 | 
						|
      [PING_TYPE + 7]: 6,
 | 
						|
      [PING_TYPE + 6]: 5,
 | 
						|
      [PING_TYPE + 5]: 4,
 | 
						|
      [PING_TYPE + 4]: 3,
 | 
						|
      [PING_TYPE + 3]: 2,
 | 
						|
      [PING_TYPE + 2]: 1,
 | 
						|
    },
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_discardedForSizePending() {
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
  PingServer.clearRequests();
 | 
						|
 | 
						|
  const PING_TYPE = "discarded-for-size-pending";
 | 
						|
 | 
						|
  const OVERSIZED_PING_ID = "9b21ec8f-f762-4d28-a2c1-44e1c4694f24";
 | 
						|
  // Create a pending oversized ping.
 | 
						|
  let overSizedPayload = generateRandomString(2 * 1024 * 1024);
 | 
						|
  const OVERSIZED_PING = {
 | 
						|
    id: OVERSIZED_PING_ID,
 | 
						|
    type: PING_TYPE,
 | 
						|
    creationDate: new Date().toISOString(),
 | 
						|
    // Generate a 2MB string to use as the ping payload.
 | 
						|
    payload: overSizedPayload,
 | 
						|
  };
 | 
						|
 | 
						|
  // Test loadPendingPing.
 | 
						|
  await TelemetryStorage.savePendingPing(OVERSIZED_PING);
 | 
						|
  // Try to manually load the oversized ping.
 | 
						|
  await Assert.rejects(
 | 
						|
    TelemetryStorage.loadPendingPing(OVERSIZED_PING_ID),
 | 
						|
    /loadPendingPing - exceeded the maximum ping size/,
 | 
						|
    "The oversized ping should have been pruned."
 | 
						|
  );
 | 
						|
 | 
						|
  let ping = await PingServer.promiseNextPing();
 | 
						|
  checkHealthPingStructure(ping, {
 | 
						|
    [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
 | 
						|
      "<unknown>": 1,
 | 
						|
    },
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.IMMEDIATE,
 | 
						|
  });
 | 
						|
 | 
						|
  // Test _scanPendingPings.
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
  await TelemetryStorage.savePendingPing(OVERSIZED_PING);
 | 
						|
  await TelemetryStorage.loadPendingPingList();
 | 
						|
 | 
						|
  ping = await PingServer.promiseNextPing();
 | 
						|
  checkHealthPingStructure(ping, {
 | 
						|
    [TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
 | 
						|
      "<unknown>": 1,
 | 
						|
    },
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.IMMEDIATE,
 | 
						|
  });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_usePingSenderOnShutdown() {
 | 
						|
  if (
 | 
						|
    gIsAndroid ||
 | 
						|
    (AppConstants.platform == "linux" && OS.Constants.Sys.bits == 32)
 | 
						|
  ) {
 | 
						|
    // We don't support the pingsender on Android, yet, see bug 1335917.
 | 
						|
    // We also don't support the pingsender testing on Treeherder for
 | 
						|
    // Linux 32 bit (due to missing libraries). So skip it there too.
 | 
						|
    // See bug 1310703 comment 78.
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  TelemetryHealthPing.testReset();
 | 
						|
  await TelemetrySend.reset();
 | 
						|
  PingServer.clearRequests();
 | 
						|
 | 
						|
  // This first failure should immediately trigger a ping.
 | 
						|
  // After this, subsequent failures should be throttled.
 | 
						|
  await TelemetryHealthPing.recordSendFailure("testFailure");
 | 
						|
  await PingServer.promiseNextPing();
 | 
						|
 | 
						|
  TelemetryHealthPing.recordSendFailure("testFailure");
 | 
						|
  let nextRequest = PingServer.promiseNextRequest();
 | 
						|
 | 
						|
  await TelemetryController.testReset();
 | 
						|
  await TelemetryController.testShutdown();
 | 
						|
  let request = await nextRequest;
 | 
						|
  let ping = decodeRequestPayload(request);
 | 
						|
 | 
						|
  checkHealthPingStructure(ping, {
 | 
						|
    [TelemetryHealthPing.FailureType.SEND_FAILURE]: {
 | 
						|
      testFailure: 1,
 | 
						|
    },
 | 
						|
    os: TelemetryHealthPing.OsInfo,
 | 
						|
    reason: TelemetryHealthPing.Reason.SHUT_DOWN,
 | 
						|
  });
 | 
						|
 | 
						|
  // Check that the health ping is sent at shutdown using the pingsender.
 | 
						|
  Assert.equal(
 | 
						|
    request.getHeader("User-Agent"),
 | 
						|
    "pingsender/1.0",
 | 
						|
    "Should have received the correct user agent string."
 | 
						|
  );
 | 
						|
  Assert.equal(
 | 
						|
    request.getHeader("X-PingSender-Version"),
 | 
						|
    "1.0",
 | 
						|
    "Should have received the correct PingSender version string."
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function cleanup() {
 | 
						|
  await PingServer.stop();
 | 
						|
});
 |