Backed out 3 changesets (bug 989137) for a sudden influx of xpcshell failures

CLOSED TREE

Backed out changeset 831a3ccce100 (bug 989137)
Backed out changeset d3053c4e4c51 (bug 989137)
Backed out changeset 7e410c1d61e6 (bug 989137)
This commit is contained in:
Phil Ringnalda 2014-04-05 20:30:24 -07:00
Родитель d0164b11b6
Коммит 8d395ba254
7 изменённых файлов: 71 добавлений и 405 удалений

Просмотреть файл

@ -98,10 +98,6 @@ let gPolicyCounter = 0;
let gExperimentsCounter = 0;
let gExperimentEntryCounter = 0;
// Tracks active AddonInstall we know about so we can deny external
// installs.
let gActiveInstallURLs = new Set();
let gLogger;
let gLogDumping = false;
@ -212,10 +208,6 @@ function uninstallAddons(addons) {
AddonManager.addAddonListener(listener);
for (let addon of addons) {
// Disabling the add-on before uninstalling is necessary to cause tests to
// pass. This might be indicative of a bug in XPIProvider.
// TODO follow up in bug 992396.
addon.userDisabled = true;
addon.uninstall();
}
@ -367,7 +359,7 @@ Experiments.Experiments.prototype = {
AsyncShutdown.profileBeforeChange.addBlocker("Experiments.jsm shutdown",
this.uninit.bind(this));
this._startWatchingAddons();
AddonManager.addAddonListener(this);
this._loadTask = Task.spawn(this._loadFromCache.bind(this));
this._loadTask.then(
@ -388,7 +380,7 @@ Experiments.Experiments.prototype = {
*/
uninit: function () {
if (!this._shutdown) {
this._stopWatchingAddons();
AddonManager.removeAddonListener(this);
gPrefs.ignore(PREF_LOGGING, configureLogging);
gPrefs.ignore(PREF_MANIFEST_URI, this.updateManifest, this);
@ -408,16 +400,6 @@ Experiments.Experiments.prototype = {
return Promise.resolve();
},
_startWatchingAddons: function () {
AddonManager.addAddonListener(this);
AddonManager.addInstallListener(this);
},
_stopWatchingAddons: function () {
AddonManager.removeInstallListener(this);
AddonManager.removeAddonListener(this);
},
/**
* Throws an exception if we've already shut down.
*/
@ -662,40 +644,6 @@ Experiments.Experiments.prototype = {
this.disableExperiment();
},
onInstallStarted: function (install) {
if (install.addon.type != "experiment") {
return;
}
// We want to be in control of all experiment add-ons: reject installs
// for add-ons that we don't know about.
// We have a race condition of sorts to worry about here. We have 2
// onInstallStarted listeners. This one (the global one) and the one
// created as part of ExperimentEntry._installAddon. Because of the order
// they are registered in, this one likely executes first. Unfortunately,
// this means that the add-on ID is not yet set on the ExperimentEntry.
// So, we can't just look at this._trackedAddonIds because the new experiment
// will have its add-on ID set to null. We work around this by storing a
// identifying field - the source URL of the install - in a module-level
// variable (so multiple Experiments instances doesn't cancel each other
// out).
if (this._trackedAddonIds.has(install.addon.id)) {
return;
}
if (gActiveInstallURLs.has(install.sourceURI.spec)) {
this._log.info("onInstallStarted allowing install because install " +
"tracked by us.");
return;
}
this._log.warn("onInstallStarted cancelling install of unknown " +
"experiment add-on: " + install.addon.id);
return false;
},
// END OF ADD-ON LISTENERS.
_getExperimentByAddonId: function (addonId) {
@ -903,13 +851,6 @@ Experiments.Experiments.prototype = {
return this._run();
},
/**
* The Set of add-on IDs that we know about from manifests.
*/
get _trackedAddonIds() {
return new Set([e._addonId for ([,e] of this._experiments) if (e._addonId)]);
},
/*
* Task function to check applicability of experiments, disable the active
* experiment if needed and activate the first applicable candidate.
@ -934,7 +875,7 @@ Experiments.Experiments.prototype = {
// should have some record of it. In the end, we decide to discard all
// knowledge for these unknown experiment add-ons.
let installedExperiments = yield installedExperimentAddons();
let expectedAddonIds = this._trackedAddonIds;
let expectedAddonIds = new Set([e._addonId for ([,e] of this._experiments)]);
let unknownAddons = [a for (a of installedExperiments) if (!expectedAddonIds.has(a.id))];
if (unknownAddons.length) {
this._log.warn("_evaluateExperiments() - unknown add-ons in AddonManager: " +
@ -976,8 +917,6 @@ Experiments.Experiments.prototype = {
}
this._dirty = true;
activeChanged = true;
} else {
yield activeExperiment.ensureActive();
}
} finally {
this._pendingUninstall = null;
@ -1463,14 +1402,11 @@ Experiments.ExperimentEntry.prototype = {
let install = yield addonInstallForURL(this._manifestData.xpiURL,
this._manifestData.xpiHash);
gActiveInstallURLs.add(install.sourceURI.spec);
let failureHandler = (install, handler) => {
let message = "AddonInstall " + handler + " for " + this.id + ", state=" +
(install.state || "?") + ", error=" + install.error;
this._log.error("_installAddon() - " + message);
this._failedStart = true;
gActiveInstallURLs.delete(install.sourceURI.spec);
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
[TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]);
@ -1479,8 +1415,6 @@ Experiments.ExperimentEntry.prototype = {
};
let listener = {
_expectedID: null,
onDownloadEnded: install => {
this._log.trace("_installAddon() - onDownloadEnded for " + this.id);
@ -1505,12 +1439,13 @@ Experiments.ExperimentEntry.prototype = {
this._log.error("_installAddon() - onInstallStarted, wrong addon type");
return false;
}
// Experiment add-ons default to userDisabled = true.
install.addon.userDisabled = false;
},
onInstallEnded: install => {
this._log.trace("_installAddon() - install ended for " + this.id);
gActiveInstallURLs.delete(install.sourceURI.spec);
this._lastChangedDate = this._policy.now();
this._startDate = this._policy.now();
this._enabled = true;
@ -1524,26 +1459,6 @@ Experiments.ExperimentEntry.prototype = {
this._description = addon.description || "";
this._homepageURL = addon.homepageURL || "";
// Experiment add-ons default to userDisabled=true. Enable if needed.
if (addon.userDisabled) {
this._log.trace("Add-on is disabled. Enabling.");
listener._expectedID = addon.id;
AddonManager.addAddonListener(listener);
addon.userDisabled = false;
} else {
this._log.trace("Add-on is enabled. start() completed.");
deferred.resolve();
}
},
onEnabled: addon => {
this._log.info("onEnabled() for " + addon.id);
if (addon.id != listener._expectedID) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve();
},
};
@ -1581,7 +1496,7 @@ Experiments.ExperimentEntry.prototype = {
this._endDate = now;
};
this._getAddon().then((addon) => {
AddonManager.getAddonByID(this._addonId, addon => {
if (!addon) {
let message = "could not get Addon for " + this.id;
this._log.warn("stop() - " + message);
@ -1598,61 +1513,6 @@ Experiments.ExperimentEntry.prototype = {
return deferred.promise;
},
/**
* Try to ensure this experiment is active.
*
* The returned promise will be resolved if the experiment is active
* in the Addon Manager or rejected if it isn't.
*
* @return Promise<>
*/
ensureActive: Task.async(function* () {
this._log.trace("ensureActive() for " + this.id);
let addon = yield this._getAddon();
if (!addon) {
this._log.warn("Experiment is not installed: " + this._addonId);
throw new Error("Experiment is not installed: " + this._addonId);
}
// User disabled likely means the experiment is disabled at startup,
// since the permissions don't allow it to be disabled by the user.
if (!addon.userDisabled) {
return;
}
let deferred = Promise.defer();
let listener = {
onEnabled: enabledAddon => {
if (enabledAddon.id != addon.id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve();
},
};
this._log.info("Activating add-on: " + addon.id);
AddonManager.addAddonListener(listener);
addon.userDisabled = false;
yield deferred.promise;
}),
/**
* Obtain the underlying Addon from the Addon Manager.
*
* @return Promise<Addon|null>
*/
_getAddon: function () {
let deferred = Promise.defer();
AddonManager.getAddonByID(this._addonId, deferred.resolve);
return deferred.promise;
},
_logTermination: function (terminationKind, terminationReason) {
if (terminationKind === undefined) {
return;

Просмотреть файл

@ -147,6 +147,61 @@ function loadAddonManager() {
startupManager();
}
// Install addon and return a Promise<boolean> that is
// resolve with true on success, false otherwise.
function installAddon(url, hash) {
let deferred = Promise.defer();
let success = () => deferred.resolve(true);
let fail = () => deferred.resolve(false);
let listener = {
onDownloadCancelled: fail,
onDownloadFailed: fail,
onInstallCancelled: fail,
onInstallFailed: fail,
onInstallEnded: success,
};
let installCallback = install => {
install.addListener(listener);
install.install();
};
AddonManager.getInstallForURL(url, installCallback,
"application/x-xpinstall", hash);
return deferred.promise;
}
// Uninstall addon and return a Promise<boolean> that is
// resolve with true on success, false otherwise.
function uninstallAddon(id) {
let deferred = Promise.defer();
AddonManager.getAddonByID(id, addon => {
if (!addon) {
deferred.resolve(false);
}
let listener = {};
let handler = addon => {
if (addon.id !== id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve(true);
};
listener.onUninstalled = handler;
listener.onDisabled = handler;
AddonManager.addAddonListener(listener);
addon.uninstall();
});
return deferred.promise;
}
function getExperimentAddons() {
let deferred = Promise.defer();

Просмотреть файл

@ -86,12 +86,6 @@ add_task(function* test_startStop() {
});
let experiment = new Experiments.ExperimentEntry(gPolicy);
experiment.initFromManifestData(manifestData);
// We need to associate it with the singleton so the onInstallStarted
// Addon Manager listener will know about it.
Experiments.instance()._experiments = new Map();
Experiments.instance()._experiments.set(experiment.id, experiment);
let result;
defineNow(gPolicy, baseDate);
@ -99,43 +93,25 @@ add_task(function* test_startStop() {
Assert.equal(result.applicable, false, "Experiment should not be applicable.");
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY));
result = yield isApplicable(experiment);
Assert.equal(result.applicable, true, "Experiment should now be applicable.");
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
yield experiment.start();
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
yield experiment.stop();
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager.");
yield experiment.start();
addons = yield getExperimentAddons();
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
Assert.ok(addons[0].isActive, "The add-on is active.");
let result = yield experiment._shouldStop();
Assert.equal(result.shouldStop, false, "shouldStop should be false.");
let maybeStop = yield experiment.maybeStop();
Assert.equal(maybeStop, false, "Experiment should not have been stopped.");
Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "Experiment still in add-ons manager.");
Assert.ok(addons[0].isActive, "The add-on is still active.");
defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
result = yield experiment._shouldStop();
@ -143,6 +119,4 @@ add_task(function* test_startStop() {
maybeStop = yield experiment.maybeStop();
Assert.equal(maybeStop, true, "Experiment should have been stopped.");
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");
});

Просмотреть файл

@ -4,8 +4,6 @@
"use strict";
Cu.import("resource://testing-common/httpd.js");
Cu.import("resource://testing-common/AddonManagerTesting.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
"resource:///modules/experiments/Experiments.jsm");
@ -153,8 +151,6 @@ add_task(function* test_getExperiments() {
"Experiments observer should not have been called yet.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
// Trigger update, clock set for experiment 1 to start.
@ -168,8 +164,6 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "An experiment add-on was installed.");
experimentListData[1].active = true;
experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
@ -193,8 +187,6 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
experimentListData[1].active = false;
experimentListData[1].endDate = now.getTime();
@ -219,8 +211,6 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "An experiment add-on is installed.");
experimentListData[0].active = true;
experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
@ -246,8 +236,6 @@ add_task(function* test_getExperiments() {
list = yield experiments.getExperiments();
Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "No experiments add-ons are installed.");
experimentListData[0].active = false;
experimentListData[0].endDate = now.getTime();
@ -313,6 +301,11 @@ add_task(function* test_addonAlreadyInstalled() {
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Install conflicting addon.
let installed = yield installAddon(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
Assert.ok(installed, "Addon should have been installed.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
@ -327,19 +320,6 @@ add_task(function* test_addonAlreadyInstalled() {
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
Assert.equal(list[0].active, true, "Experiment 1 should be active.");
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "1 add-on is installed.");
// Install conflicting addon.
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "1 add-on is installed.");
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should still have 1 entry.");
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
Assert.equal(list[0].active, true, "Experiment 1 should be active.");
// Cleanup.
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
@ -1346,8 +1326,9 @@ add_task(function* test_unexpectedUninstall() {
// Uninstall the addon through the addon manager instead of stopping it through
// the experiments API.
yield AddonTestUtils.uninstallAddonByID(EXPERIMENT1_ID);
let success = yield uninstallAddon(EXPERIMENT1_ID);
yield experiments._mainTask;
Assert.ok(success, "Addon should have been uninstalled.");
yield experiments.notify();
@ -1370,12 +1351,7 @@ add_task(function* testUnknownExperimentsUninstalled() {
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are present.");
// Simulate us not listening.
experiments._stopWatchingAddons();
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
experiments._startWatchingAddons();
yield installAddon(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "Experiment 1 installed via AddonManager");
@ -1396,80 +1372,3 @@ add_task(function* testUnknownExperimentsUninstalled() {
yield experiments.uninit();
yield removeCacheFile();
});
// If someone else installs an experiment add-on, we detect and stop that.
add_task(function* testForeignExperimentInstall() {
let experiments = new Experiments.Experiments(gPolicy);
gManifestObject = {
"version": 1,
experiments: [],
};
yield experiments.init();
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons present.");
let failed;
try {
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
} catch (ex) {
failed = true;
}
Assert.ok(failed, "Add-on install should not have completed successfully");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Add-on install should have been cancelled.");
yield experiments.uninit();
yield removeCacheFile();
});
// Experiment add-ons will be disabled after Addon Manager restarts. Ensure
// we enable them automatically.
add_task(function* testEnabledAfterRestart() {
let experiments = new Experiments.Experiments(gPolicy);
gManifestObject = {
"version": 1,
experiments: [
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
startTime: gPolicy.now().getTime() / 1000 - 60,
endTime: gPolicy.now().getTime() / 1000 + 60,
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
],
};
let addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Precondition: No experimenta add-ons installed.");
yield experiments.updateManifest();
let fromManifest = yield experiments.getExperiments();
Assert.equal(fromManifest.length, 1, "A single experiment is known.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
Assert.ok(addons[0].isActive, "That experiment is active.");
dump("Restarting Addon Manager\n");
experiments._stopWatchingAddons();
restartManager();
experiments._startWatchingAddons();
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "The experiment is still there after restart.");
Assert.ok(addons[0].userDisabled, "But it is disabled.");
Assert.equal(addons[0].isActive, false, "And not active.");
yield experiments.updateManifest();
Assert.ok(addons[0].isActive, "It activates when the manifest is evaluated.");
yield experiments.uninit();
yield removeCacheFile();
});

Просмотреть файл

@ -1,105 +0,0 @@
/* 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/. */
// This file is a test-only JSM containing utility methods for
// interacting with the add-ons manager.
"use strict";
this.EXPORTED_SYMBOLS = [
"AddonTestUtils",
];
const {utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
this.AddonTestUtils = {
/**
* Uninstall an add-on that is specified by its ID.
*
* The returned promise resolves on successful uninstall and rejects
* if the add-on is not unknown.
*
* @return Promise<restartRequired>
*/
uninstallAddonByID: function (id) {
let deferred = Promise.defer();
AddonManager.getAddonByID(id, (addon) => {
if (!addon) {
deferred.reject(new Error("Add-on is not known: " + id));
return;
}
let listener = {
onUninstalling: function (addon, needsRestart) {
if (addon.id != id) {
return;
}
if (needsRestart) {
AddonManager.removeAddonListener(listener);
deferred.resolve(true);
}
},
onUninstalled: function (addon) {
if (addon.id != id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.resolve(false);
},
onOperationCancelled: function (addon) {
if (addon.id != id) {
return;
}
AddonManager.removeAddonListener(listener);
deferred.reject(new Error("Uninstall cancelled."));
},
};
AddonManager.addAddonListener(listener);
addon.uninstall();
});
return deferred.promise;
},
/**
* Install an XPI add-on from a URL.
*
* @return Promise<addon>
*/
installXPIFromURL: function (url, hash, name, iconURL, version) {
let deferred = Promise.defer();
AddonManager.getInstallForURL(url, (install) => {
let fail = () => { deferred.reject(new Error("Add-on install failed.")) };
let listener = {
onDownloadCancelled: fail,
onDownloadFailed: fail,
onInstallCancelled: fail,
onInstallFailed: fail,
onInstallEnded: function (install, addon) {
deferred.resolve(addon);
},
};
install.addListener(listener);
install.install();
}, "application/x-xpinstall", hash, name, iconURL, version);
return deferred.promise;
},
};

Просмотреть файл

@ -2,8 +2,6 @@
# 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/.
TESTING_JS_MODULES := AddonManagerTesting.jsm
ADDONSRC = $(srcdir)/addons
TESTROOT = $(CURDIR)/$(DEPTH)/_tests/xpcshell/$(relativesrcdir)
TESTXPI = $(TESTROOT)/xpcshell/addons

Просмотреть файл

@ -5,7 +5,6 @@
let gManagerWindow;
let gCategoryUtilities;
let gInstalledAddons = [];
let gContext = this;
function test() {
waitForExplicitFinish();
@ -14,14 +13,6 @@ function test() {
gManagerWindow = win;
gCategoryUtilities = new CategoryUtilities(win);
// The Experiments Manager will interfere with us by preventing installs
// of experiments it doesn't know about. We remove it from the equation
// because here we are only concerned with core Addon Manager operation,
// not the super set Experiments Manager has imposed.
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
Components.utils.import("resource:///modules/experiments/Experiments.jsm", gContext);
gContext.Experiments.instance()._stopWatchingAddons();
}
run_next_test();
});
}
@ -31,13 +22,7 @@ function end_test() {
addon.uninstall();
}
close_manager(gManagerWindow, () => {
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
gContext.Experiments.instance()._startWatchingAddons();
}
finish();
});
close_manager(gManagerWindow, finish);
}
// On an empty profile with no experiments, the experiment category