summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/TelemetryEnvironmentTesting.sys.mjs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/telemetry/tests/unit/TelemetryEnvironmentTesting.sys.mjs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/TelemetryEnvironmentTesting.sys.mjs')
-rw-r--r--toolkit/components/telemetry/tests/unit/TelemetryEnvironmentTesting.sys.mjs875
1 files changed, 875 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/unit/TelemetryEnvironmentTesting.sys.mjs b/toolkit/components/telemetry/tests/unit/TelemetryEnvironmentTesting.sys.mjs
new file mode 100644
index 0000000000..0df6dfc2c1
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/TelemetryEnvironmentTesting.sys.mjs
@@ -0,0 +1,875 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+ Assert: "resource://testing-common/Assert.sys.mjs",
+ // AttributionCode is only needed for Firefox
+ AttributionCode: "resource:///modules/AttributionCode.sys.mjs",
+
+ MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs",
+});
+
+const gIsWindows = AppConstants.platform == "win";
+const gIsMac = AppConstants.platform == "macosx";
+const gIsAndroid = AppConstants.platform == "android";
+const gIsLinux = AppConstants.platform == "linux";
+
+const MILLISECONDS_PER_MINUTE = 60 * 1000;
+const MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE;
+const MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR;
+
+const PLATFORM_VERSION = "1.9.2";
+const APP_VERSION = "1";
+const APP_ID = "xpcshell@tests.mozilla.org";
+const APP_NAME = "XPCShell";
+
+const DISTRIBUTION_ID = "distributor-id";
+const DISTRIBUTION_VERSION = "4.5.6b";
+const DISTRIBUTOR_NAME = "Some Distributor";
+const DISTRIBUTOR_CHANNEL = "A Channel";
+const PARTNER_NAME = "test";
+const PARTNER_ID = "NicePartner-ID-3785";
+
+// The profile reset date, in milliseconds (Today)
+const PROFILE_RESET_DATE_MS = Date.now();
+// The profile creation date, in milliseconds (Yesterday).
+const PROFILE_FIRST_USE_MS = PROFILE_RESET_DATE_MS - MILLISECONDS_PER_DAY;
+const PROFILE_CREATION_DATE_MS = PROFILE_FIRST_USE_MS - MILLISECONDS_PER_DAY;
+
+const GFX_VENDOR_ID = "0xabcd";
+const GFX_DEVICE_ID = "0x1234";
+
+const EXPECTED_HDD_FIELDS = ["profile", "binary", "system"];
+
+// Valid attribution code to write so that settings.attribution can be tested.
+const ATTRIBUTION_CODE = "source%3Dgoogle.com%26dlsource%3Dunittest";
+
+function truncateToDays(aMsec) {
+ return Math.floor(aMsec / MILLISECONDS_PER_DAY);
+}
+
+var SysInfo = {
+ overrides: {},
+
+ getProperty(name) {
+ // Assert.ok(false, "Mock SysInfo: " + name + ", " + JSON.stringify(this.overrides));
+ if (name in this.overrides) {
+ return this.overrides[name];
+ }
+
+ return this._genuine.QueryInterface(Ci.nsIPropertyBag).getProperty(name);
+ },
+
+ getPropertyAsACString(name) {
+ return this.get(name);
+ },
+
+ getPropertyAsUint32(name) {
+ return this.get(name);
+ },
+
+ get(name) {
+ return this._genuine.QueryInterface(Ci.nsIPropertyBag2).get(name);
+ },
+
+ get diskInfo() {
+ return this._genuine.QueryInterface(Ci.nsISystemInfo).diskInfo;
+ },
+
+ get osInfo() {
+ return this._genuine.QueryInterface(Ci.nsISystemInfo).osInfo;
+ },
+
+ get processInfo() {
+ return this._genuine.QueryInterface(Ci.nsISystemInfo).processInfo;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIPropertyBag2", "nsISystemInfo"]),
+};
+
+/**
+ * TelemetryEnvironmentTesting - tools for testing the telemetry environment
+ * reporting.
+ */
+export var TelemetryEnvironmentTesting = {
+ EXPECTED_HDD_FIELDS,
+
+ init(appInfo) {
+ this.appInfo = appInfo;
+ },
+
+ setSysInfoOverrides(overrides) {
+ SysInfo.overrides = overrides;
+ },
+
+ spoofGfxAdapter() {
+ try {
+ let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(
+ Ci.nsIGfxInfoDebug
+ );
+ gfxInfo.spoofVendorID(GFX_VENDOR_ID);
+ gfxInfo.spoofDeviceID(GFX_DEVICE_ID);
+ } catch (x) {
+ // If we can't test gfxInfo, that's fine, we'll note it later.
+ }
+ },
+
+ spoofProfileReset() {
+ return IOUtils.writeJSON(
+ PathUtils.join(PathUtils.profileDir, "times.json"),
+ {
+ created: PROFILE_CREATION_DATE_MS,
+ reset: PROFILE_RESET_DATE_MS,
+ firstUse: PROFILE_FIRST_USE_MS,
+ }
+ );
+ },
+
+ spoofPartnerInfo() {
+ let prefsToSpoof = {};
+ prefsToSpoof["distribution.id"] = DISTRIBUTION_ID;
+ prefsToSpoof["distribution.version"] = DISTRIBUTION_VERSION;
+ prefsToSpoof["app.distributor"] = DISTRIBUTOR_NAME;
+ prefsToSpoof["app.distributor.channel"] = DISTRIBUTOR_CHANNEL;
+ prefsToSpoof["app.partner.test"] = PARTNER_NAME;
+ prefsToSpoof["mozilla.partner.id"] = PARTNER_ID;
+
+ // Spoof the preferences.
+ for (let pref in prefsToSpoof) {
+ Services.prefs
+ .getDefaultBranch(null)
+ .setStringPref(pref, prefsToSpoof[pref]);
+ }
+ },
+
+ async spoofAttributionData() {
+ if (gIsWindows) {
+ lazy.AttributionCode._clearCache();
+ await lazy.AttributionCode.writeAttributionFile(ATTRIBUTION_CODE);
+ } else if (gIsMac) {
+ lazy.AttributionCode._clearCache();
+ const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+ );
+ await MacAttribution.setAttributionString(ATTRIBUTION_CODE);
+ }
+ },
+
+ async cleanupAttributionData() {
+ if (gIsWindows) {
+ lazy.AttributionCode.attributionFile.remove(false);
+ lazy.AttributionCode._clearCache();
+ } else if (gIsMac) {
+ const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+ );
+ await MacAttribution.delAttributionString();
+ }
+ },
+
+ registerFakeSysInfo() {
+ lazy.MockRegistrar.register("@mozilla.org/system-info;1", SysInfo);
+ },
+
+ /**
+ * Check that a value is a string and not empty.
+ *
+ * @param aValue The variable to check.
+ * @return True if |aValue| has type "string" and is not empty, False otherwise.
+ */
+ checkString(aValue) {
+ return typeof aValue == "string" && aValue != "";
+ },
+
+ /**
+ * If value is non-null, check if it's a valid string.
+ *
+ * @param aValue The variable to check.
+ * @return True if it's null or a valid string, false if it's non-null and an invalid
+ * string.
+ */
+ checkNullOrString(aValue) {
+ if (aValue) {
+ return this.checkString(aValue);
+ } else if (aValue === null) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * If value is non-null, check if it's a boolean.
+ *
+ * @param aValue The variable to check.
+ * @return True if it's null or a valid boolean, false if it's non-null and an invalid
+ * boolean.
+ */
+ checkNullOrBool(aValue) {
+ return aValue === null || typeof aValue == "boolean";
+ },
+
+ checkBuildSection(data) {
+ const expectedInfo = {
+ applicationId: APP_ID,
+ applicationName: APP_NAME,
+ buildId: this.appInfo.appBuildID,
+ version: APP_VERSION,
+ vendor: "Mozilla",
+ platformVersion: PLATFORM_VERSION,
+ xpcomAbi: "noarch-spidermonkey",
+ };
+
+ lazy.Assert.ok(
+ "build" in data,
+ "There must be a build section in Environment."
+ );
+
+ for (let f in expectedInfo) {
+ lazy.Assert.ok(
+ this.checkString(data.build[f]),
+ f + " must be a valid string."
+ );
+ lazy.Assert.equal(
+ data.build[f],
+ expectedInfo[f],
+ f + " must have the correct value."
+ );
+ }
+
+ // Make sure architecture is in the environment.
+ lazy.Assert.ok(this.checkString(data.build.architecture));
+
+ lazy.Assert.equal(
+ data.build.updaterAvailable,
+ AppConstants.MOZ_UPDATER,
+ "build.updaterAvailable must equal AppConstants.MOZ_UPDATER"
+ );
+ },
+
+ checkSettingsSection(data) {
+ const EXPECTED_FIELDS_TYPES = {
+ blocklistEnabled: "boolean",
+ e10sEnabled: "boolean",
+ e10sMultiProcesses: "number",
+ fissionEnabled: "boolean",
+ intl: "object",
+ locale: "string",
+ telemetryEnabled: "boolean",
+ update: "object",
+ userPrefs: "object",
+ };
+
+ lazy.Assert.ok(
+ "settings" in data,
+ "There must be a settings section in Environment."
+ );
+
+ for (let f in EXPECTED_FIELDS_TYPES) {
+ lazy.Assert.equal(
+ typeof data.settings[f],
+ EXPECTED_FIELDS_TYPES[f],
+ f + " must have the correct type."
+ );
+ }
+
+ // This property is not always present, but when it is, it must be a number.
+ if ("launcherProcessState" in data.settings) {
+ lazy.Assert.equal(typeof data.settings.launcherProcessState, "number");
+ }
+
+ // Check "addonCompatibilityCheckEnabled" separately.
+ lazy.Assert.equal(
+ data.settings.addonCompatibilityCheckEnabled,
+ lazy.AddonManager.checkCompatibility
+ );
+
+ // Check "isDefaultBrowser" separately, as it is not available on Android an can either be
+ // null or boolean on other platforms.
+ if (gIsAndroid) {
+ lazy.Assert.ok(
+ !("isDefaultBrowser" in data.settings),
+ "Must not be available on Android."
+ );
+ } else if ("isDefaultBrowser" in data.settings) {
+ // isDefaultBrowser might not be available in the payload, since it's
+ // gathered after the session was restored.
+ lazy.Assert.ok(this.checkNullOrBool(data.settings.isDefaultBrowser));
+ }
+
+ // Check "channel" separately, as it can either be null or string.
+ let update = data.settings.update;
+ lazy.Assert.ok(this.checkNullOrString(update.channel));
+ lazy.Assert.equal(typeof update.enabled, "boolean");
+ lazy.Assert.equal(typeof update.autoDownload, "boolean");
+ lazy.Assert.equal(typeof update.background, "boolean");
+
+ // Check sandbox settings exist and make sense
+ if (data.settings.sandbox.effectiveContentProcessLevel !== null) {
+ lazy.Assert.equal(
+ typeof data.settings.sandbox.effectiveContentProcessLevel,
+ "number",
+ "sandbox.effectiveContentProcessLevel must have the correct type"
+ );
+ }
+
+ if (data.settings.sandbox.contentWin32kLockdownState !== null) {
+ lazy.Assert.equal(
+ typeof data.settings.sandbox.contentWin32kLockdownState,
+ "number",
+ "sandbox.contentWin32kLockdownState must have the correct type"
+ );
+
+ let win32kLockdownState =
+ data.settings.sandbox.contentWin32kLockdownState;
+ lazy.Assert.ok(win32kLockdownState >= 1 && win32kLockdownState <= 17);
+ }
+
+ // Check "defaultSearchEngine" separately, as it can either be undefined or string.
+ if ("defaultSearchEngine" in data.settings) {
+ this.checkString(data.settings.defaultSearchEngine);
+ lazy.Assert.equal(typeof data.settings.defaultSearchEngineData, "object");
+ }
+
+ if ("defaultPrivateSearchEngineData" in data.settings) {
+ lazy.Assert.equal(
+ typeof data.settings.defaultPrivateSearchEngineData,
+ "object"
+ );
+ }
+
+ if ((gIsWindows || gIsMac) && AppConstants.MOZ_BUILD_APP == "browser") {
+ lazy.Assert.equal(typeof data.settings.attribution, "object");
+ lazy.Assert.equal(data.settings.attribution.source, "google.com");
+ lazy.Assert.equal(data.settings.attribution.dlsource, "unittest");
+ }
+
+ this.checkIntlSettings(data.settings);
+ },
+
+ checkIntlSettings({ intl }) {
+ let fields = [
+ "requestedLocales",
+ "availableLocales",
+ "appLocales",
+ "acceptLanguages",
+ ];
+
+ for (let field of fields) {
+ lazy.Assert.ok(Array.isArray(intl[field]), `${field} is an array`);
+ }
+
+ // These fields may be null if they aren't ready yet. This is mostly to deal
+ // with test failures on Android, but they aren't guaranteed to exist.
+ let optionalFields = ["systemLocales", "regionalPrefsLocales"];
+
+ for (let field of optionalFields) {
+ let isArray = Array.isArray(intl[field]);
+ let isNull = intl[field] === null;
+ lazy.Assert.ok(isArray || isNull, `${field} is an array or null`);
+ }
+ },
+
+ checkProfileSection(data) {
+ lazy.Assert.ok(
+ "profile" in data,
+ "There must be a profile section in Environment."
+ );
+ lazy.Assert.equal(
+ data.profile.creationDate,
+ truncateToDays(PROFILE_CREATION_DATE_MS)
+ );
+ lazy.Assert.equal(
+ data.profile.resetDate,
+ truncateToDays(PROFILE_RESET_DATE_MS)
+ );
+ lazy.Assert.equal(
+ data.profile.firstUseDate,
+ truncateToDays(PROFILE_FIRST_USE_MS)
+ );
+ },
+
+ checkPartnerSection(data, isInitial) {
+ const EXPECTED_FIELDS = {
+ distributionId: DISTRIBUTION_ID,
+ distributionVersion: DISTRIBUTION_VERSION,
+ partnerId: PARTNER_ID,
+ distributor: DISTRIBUTOR_NAME,
+ distributorChannel: DISTRIBUTOR_CHANNEL,
+ };
+
+ lazy.Assert.ok(
+ "partner" in data,
+ "There must be a partner section in Environment."
+ );
+
+ for (let f in EXPECTED_FIELDS) {
+ let expected = isInitial ? null : EXPECTED_FIELDS[f];
+ lazy.Assert.strictEqual(
+ data.partner[f],
+ expected,
+ f + " must have the correct value."
+ );
+ }
+
+ // Check that "partnerNames" exists and contains the correct element.
+ lazy.Assert.ok(Array.isArray(data.partner.partnerNames));
+ if (isInitial) {
+ lazy.Assert.equal(data.partner.partnerNames.length, 0);
+ } else {
+ lazy.Assert.ok(data.partner.partnerNames.includes(PARTNER_NAME));
+ }
+ },
+
+ checkGfxAdapter(data) {
+ const EXPECTED_ADAPTER_FIELDS_TYPES = {
+ description: "string",
+ vendorID: "string",
+ deviceID: "string",
+ subsysID: "string",
+ RAM: "number",
+ driver: "string",
+ driverVendor: "string",
+ driverVersion: "string",
+ driverDate: "string",
+ GPUActive: "boolean",
+ };
+
+ for (let f in EXPECTED_ADAPTER_FIELDS_TYPES) {
+ lazy.Assert.ok(f in data, f + " must be available.");
+
+ if (data[f]) {
+ // Since we have a non-null value, check if it has the correct type.
+ lazy.Assert.equal(
+ typeof data[f],
+ EXPECTED_ADAPTER_FIELDS_TYPES[f],
+ f + " must have the correct type."
+ );
+ }
+ }
+ },
+
+ checkSystemSection(data, assertProcessData) {
+ const EXPECTED_FIELDS = [
+ "memoryMB",
+ "cpu",
+ "os",
+ "hdd",
+ "gfx",
+ "appleModelId",
+ ];
+
+ lazy.Assert.ok(
+ "system" in data,
+ "There must be a system section in Environment."
+ );
+
+ // Make sure we have all the top level sections and fields.
+ for (let f of EXPECTED_FIELDS) {
+ lazy.Assert.ok(f in data.system, f + " must be available.");
+ }
+
+ lazy.Assert.ok(
+ Number.isFinite(data.system.memoryMB),
+ "MemoryMB must be a number."
+ );
+
+ if (assertProcessData) {
+ if (gIsWindows || gIsMac || gIsLinux) {
+ let EXTRA_CPU_FIELDS = [
+ "cores",
+ "model",
+ "family",
+ "stepping",
+ "l2cacheKB",
+ "l3cacheKB",
+ "speedMHz",
+ "vendor",
+ "name",
+ ];
+
+ for (let f of EXTRA_CPU_FIELDS) {
+ // Note this is testing TelemetryEnvironment.js only, not that the
+ // values are valid - null is the fallback.
+ lazy.Assert.ok(
+ f in data.system.cpu,
+ f + " must be available under cpu."
+ );
+ }
+
+ if (gIsWindows) {
+ lazy.Assert.equal(
+ typeof data.system.isWow64,
+ "boolean",
+ "isWow64 must be available on Windows and have the correct type."
+ );
+ lazy.Assert.equal(
+ typeof data.system.isWowARM64,
+ "boolean",
+ "isWowARM64 must be available on Windows and have the correct type."
+ );
+ lazy.Assert.equal(
+ typeof data.system.hasWinPackageId,
+ "boolean",
+ "hasWinPackageId must be available on Windows and have the correct type."
+ );
+ // This is only sent for Mozilla produced MSIX packages
+ lazy.Assert.ok(
+ !("winPackageFamilyName" in data.system) ||
+ data.system.winPackageFamilyName === null ||
+ typeof data.system.winPackageFamilyName === "string",
+ "winPackageFamilyName must be a string if non null"
+ );
+ lazy.Assert.ok(
+ "virtualMaxMB" in data.system,
+ "virtualMaxMB must be available."
+ );
+ lazy.Assert.ok(
+ Number.isFinite(data.system.virtualMaxMB),
+ "virtualMaxMB must be a number."
+ );
+
+ for (let f of [
+ "count",
+ "model",
+ "family",
+ "stepping",
+ "l2cacheKB",
+ "l3cacheKB",
+ "speedMHz",
+ ]) {
+ lazy.Assert.ok(
+ Number.isFinite(data.system.cpu[f]),
+ f + " must be a number if non null."
+ );
+ }
+ }
+
+ // These should be numbers if they are not null
+ for (let f of [
+ "count",
+ "model",
+ "family",
+ "stepping",
+ "l2cacheKB",
+ "l3cacheKB",
+ "speedMHz",
+ ]) {
+ lazy.Assert.ok(
+ !(f in data.system.cpu) ||
+ data.system.cpu[f] === null ||
+ Number.isFinite(data.system.cpu[f]),
+ f + " must be a number if non null."
+ );
+ }
+
+ // We insist these are available
+ for (let f of ["cores"]) {
+ lazy.Assert.ok(
+ !(f in data.system.cpu) || Number.isFinite(data.system.cpu[f]),
+ f + " must be a number if non null."
+ );
+ }
+ }
+ }
+
+ let cpuData = data.system.cpu;
+
+ lazy.Assert.ok(
+ Array.isArray(cpuData.extensions),
+ "CPU extensions must be available."
+ );
+
+ let osData = data.system.os;
+ lazy.Assert.ok(this.checkNullOrString(osData.name));
+ lazy.Assert.ok(this.checkNullOrString(osData.version));
+ lazy.Assert.ok(this.checkNullOrString(osData.locale));
+
+ // Service pack is only available on Windows.
+ if (gIsWindows) {
+ lazy.Assert.ok(
+ Number.isFinite(osData.servicePackMajor),
+ "ServicePackMajor must be a number."
+ );
+ lazy.Assert.ok(
+ Number.isFinite(osData.servicePackMinor),
+ "ServicePackMinor must be a number."
+ );
+ if ("windowsBuildNumber" in osData) {
+ // This might not be available on all Windows platforms.
+ lazy.Assert.ok(
+ Number.isFinite(osData.windowsBuildNumber),
+ "windowsBuildNumber must be a number."
+ );
+ }
+ if ("windowsUBR" in osData) {
+ // This might not be available on all Windows platforms.
+ lazy.Assert.ok(
+ osData.windowsUBR === null || Number.isFinite(osData.windowsUBR),
+ "windowsUBR must be null or a number."
+ );
+ }
+ } else if (gIsAndroid) {
+ lazy.Assert.ok(this.checkNullOrString(osData.kernelVersion));
+ } else if (gIsLinux) {
+ lazy.Assert.ok(this.checkNullOrString(osData.distro));
+ lazy.Assert.ok(this.checkNullOrString(osData.distroVersion));
+ }
+
+ for (let disk of EXPECTED_HDD_FIELDS) {
+ lazy.Assert.ok(this.checkNullOrString(data.system.hdd[disk].model));
+ lazy.Assert.ok(this.checkNullOrString(data.system.hdd[disk].revision));
+ lazy.Assert.ok(this.checkNullOrString(data.system.hdd[disk].type));
+ }
+
+ let gfxData = data.system.gfx;
+ lazy.Assert.ok("D2DEnabled" in gfxData);
+ lazy.Assert.ok("DWriteEnabled" in gfxData);
+ lazy.Assert.ok("Headless" in gfxData);
+ lazy.Assert.ok("TargetFrameRate" in gfxData);
+ lazy.Assert.equal(typeof gfxData.TargetFrameRate, "number");
+ lazy.Assert.ok("EmbeddedInFirefoxReality" in gfxData);
+ // DWriteVersion is disabled due to main thread jank and will be enabled
+ // again as part of bug 1154500.
+ // Assert.ok("DWriteVersion" in gfxData);
+ if (gIsWindows) {
+ lazy.Assert.equal(typeof gfxData.D2DEnabled, "boolean");
+ lazy.Assert.equal(typeof gfxData.DWriteEnabled, "boolean");
+ lazy.Assert.equal(typeof gfxData.EmbeddedInFirefoxReality, "boolean");
+ // As above, will be enabled again as part of bug 1154500.
+ // Assert.ok(this.checkString(gfxData.DWriteVersion));
+ }
+
+ lazy.Assert.ok("adapters" in gfxData);
+ lazy.Assert.ok(
+ !!gfxData.adapters.length,
+ "There must be at least one GFX adapter."
+ );
+ for (let adapter of gfxData.adapters) {
+ this.checkGfxAdapter(adapter);
+ }
+ lazy.Assert.equal(typeof gfxData.adapters[0].GPUActive, "boolean");
+ lazy.Assert.ok(
+ gfxData.adapters[0].GPUActive,
+ "The first GFX adapter must be active."
+ );
+
+ lazy.Assert.ok(Array.isArray(gfxData.monitors));
+ if (gIsWindows || gIsMac || gIsLinux) {
+ lazy.Assert.ok(
+ gfxData.monitors.length >= 1,
+ "There is at least one monitor."
+ );
+ lazy.Assert.equal(typeof gfxData.monitors[0].screenWidth, "number");
+ lazy.Assert.equal(typeof gfxData.monitors[0].screenHeight, "number");
+ if (gIsWindows) {
+ lazy.Assert.equal(typeof gfxData.monitors[0].refreshRate, "number");
+ lazy.Assert.equal(typeof gfxData.monitors[0].pseudoDisplay, "boolean");
+ }
+ if (gIsMac) {
+ lazy.Assert.equal(typeof gfxData.monitors[0].scale, "number");
+ }
+ }
+
+ lazy.Assert.equal(typeof gfxData.features, "object");
+ lazy.Assert.equal(typeof gfxData.features.compositor, "string");
+
+ lazy.Assert.equal(typeof gfxData.features.gpuProcess, "object");
+ lazy.Assert.equal(typeof gfxData.features.gpuProcess.status, "string");
+
+ try {
+ // If we've not got nsIGfxInfoDebug, then this will throw and stop us doing
+ // this test.
+ let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(
+ Ci.nsIGfxInfoDebug
+ );
+
+ if (gIsWindows || gIsMac) {
+ lazy.Assert.equal(GFX_VENDOR_ID, gfxData.adapters[0].vendorID);
+ lazy.Assert.equal(GFX_DEVICE_ID, gfxData.adapters[0].deviceID);
+ }
+
+ let features = gfxInfo.getFeatures();
+ lazy.Assert.equal(features.compositor, gfxData.features.compositor);
+ lazy.Assert.equal(
+ features.gpuProcess.status,
+ gfxData.features.gpuProcess.status
+ );
+ lazy.Assert.equal(features.opengl, gfxData.features.opengl);
+ lazy.Assert.equal(features.webgl, gfxData.features.webgl);
+ } catch (e) {}
+
+ if (gIsMac) {
+ lazy.Assert.ok(this.checkString(data.system.appleModelId));
+ } else {
+ lazy.Assert.ok(this.checkNullOrString(data.system.appleModelId));
+ }
+
+ // This feature is only available on Windows
+ if (AppConstants.platform == "win") {
+ lazy.Assert.ok(
+ "sec" in data.system,
+ "sec must be available under data.system"
+ );
+
+ let SEC_FIELDS = ["antivirus", "antispyware", "firewall"];
+ for (let f of SEC_FIELDS) {
+ lazy.Assert.ok(
+ f in data.system.sec,
+ f + " must be available under data.system.sec"
+ );
+
+ let value = data.system.sec[f];
+ // value is null on Windows Server
+ lazy.Assert.ok(
+ value === null || Array.isArray(value),
+ f + " must be either null or an array"
+ );
+ if (Array.isArray(value)) {
+ for (let product of value) {
+ lazy.Assert.equal(
+ typeof product,
+ "string",
+ "Each element of " + f + " must be a string"
+ );
+ }
+ }
+ }
+ }
+ },
+
+ checkActiveAddon(data, partialRecord) {
+ let signedState = "number";
+ // system add-ons have an undefined signState
+ if (data.isSystem) {
+ signedState = "undefined";
+ }
+
+ const EXPECTED_ADDON_FIELDS_TYPES = {
+ version: "string",
+ scope: "number",
+ type: "string",
+ updateDay: "number",
+ isSystem: "boolean",
+ isWebExtension: "boolean",
+ multiprocessCompatible: "boolean",
+ };
+
+ const FULL_ADDON_FIELD_TYPES = {
+ blocklisted: "boolean",
+ name: "string",
+ userDisabled: "boolean",
+ appDisabled: "boolean",
+ foreignInstall: "boolean",
+ hasBinaryComponents: "boolean",
+ installDay: "number",
+ signedState,
+ };
+
+ let fields = EXPECTED_ADDON_FIELDS_TYPES;
+ if (!partialRecord) {
+ fields = Object.assign({}, fields, FULL_ADDON_FIELD_TYPES);
+ }
+
+ for (let [name, type] of Object.entries(fields)) {
+ lazy.Assert.ok(name in data, name + " must be available.");
+ lazy.Assert.equal(
+ typeof data[name],
+ type,
+ name + " must have the correct type."
+ );
+ }
+
+ if (!partialRecord) {
+ // We check "description" separately, as it can be null.
+ lazy.Assert.ok(this.checkNullOrString(data.description));
+ }
+ },
+
+ checkTheme(data) {
+ const EXPECTED_THEME_FIELDS_TYPES = {
+ id: "string",
+ blocklisted: "boolean",
+ name: "string",
+ userDisabled: "boolean",
+ appDisabled: "boolean",
+ version: "string",
+ scope: "number",
+ foreignInstall: "boolean",
+ installDay: "number",
+ updateDay: "number",
+ };
+
+ for (let f in EXPECTED_THEME_FIELDS_TYPES) {
+ lazy.Assert.ok(f in data, f + " must be available.");
+ lazy.Assert.equal(
+ typeof data[f],
+ EXPECTED_THEME_FIELDS_TYPES[f],
+ f + " must have the correct type."
+ );
+ }
+
+ // We check "description" separately, as it can be null.
+ lazy.Assert.ok(this.checkNullOrString(data.description));
+ },
+
+ checkActiveGMPlugin(data) {
+ // GMP plugin version defaults to null until GMPDownloader runs to update it.
+ if (data.version) {
+ lazy.Assert.equal(typeof data.version, "string");
+ }
+ lazy.Assert.equal(typeof data.userDisabled, "boolean");
+ lazy.Assert.equal(typeof data.applyBackgroundUpdates, "number");
+ },
+
+ checkAddonsSection(data, expectBrokenAddons, partialAddonsRecords) {
+ const EXPECTED_FIELDS = ["activeAddons", "theme", "activeGMPlugins"];
+
+ lazy.Assert.ok(
+ "addons" in data,
+ "There must be an addons section in Environment."
+ );
+ for (let f of EXPECTED_FIELDS) {
+ lazy.Assert.ok(f in data.addons, f + " must be available.");
+ }
+
+ // Check the active addons, if available.
+ if (!expectBrokenAddons) {
+ let activeAddons = data.addons.activeAddons;
+ for (let addon in activeAddons) {
+ this.checkActiveAddon(activeAddons[addon], partialAddonsRecords);
+ }
+ }
+
+ // Check "theme" structure.
+ if (Object.keys(data.addons.theme).length !== 0) {
+ this.checkTheme(data.addons.theme);
+ }
+
+ // Check active GMPlugins
+ let activeGMPlugins = data.addons.activeGMPlugins;
+ for (let gmPlugin in activeGMPlugins) {
+ this.checkActiveGMPlugin(activeGMPlugins[gmPlugin]);
+ }
+ },
+
+ checkEnvironmentData(data, options = {}) {
+ const {
+ isInitial = false,
+ expectBrokenAddons = false,
+ assertProcessData = false,
+ } = options;
+
+ this.checkBuildSection(data);
+ this.checkSettingsSection(data);
+ this.checkProfileSection(data);
+ this.checkPartnerSection(data, isInitial);
+ this.checkSystemSection(data, assertProcessData);
+ this.checkAddonsSection(data, expectBrokenAddons);
+ },
+};