Any call to DBUS is async, but our current idle service abstraction expects all calls to be sync. As changing the API would affect quite a lot of callers, we can get away by spinning the event loop and having a timeout for DBUS only. If the DBUS proxy cannot be created during probe, we fall back to IDLE_SERVICE_XSCREENSAVER as before. Once probed successfully, poll errors are transient and do not trigger a fallback. Differential Revision: https://phabricator.services.mozilla.com/D281221
115 lines
4.6 KiB
JavaScript
115 lines
4.6 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// Exercises the GTK user idle service. The service uses the
|
|
// org.gnome.Mutter.IdleMonitor DBus backend -- the cache-and-async-poll logic
|
|
// from bug 1962438 -- whenever that service is reachable on the session bus,
|
|
// i.e. on any GNOME session, X11 or Wayland, including the Wayland CI workers.
|
|
// Otherwise the backend is XScreenSaver and only the generic smoke assertions
|
|
// apply. The active backend cannot be queried directly, so we detect it from
|
|
// the service's own MOZ_LOG output rather than guessing from the desktop
|
|
// environment (XDG_CURRENT_DESKTOP does not survive into the test process under
|
|
// CI) or the window protocol (Mutter is used under XWayland too).
|
|
|
|
const { TestUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/TestUtils.sys.mjs"
|
|
);
|
|
|
|
add_task(async function () {
|
|
const idle = Cc["@mozilla.org/widget/useridleservice;1"].getService(
|
|
Ci.nsIUserIdleService
|
|
);
|
|
|
|
// Backend-agnostic smoke checks.
|
|
const first = idle.idleTime;
|
|
Assert.equal(typeof first, "number", "idleTime is a number");
|
|
Assert.greaterOrEqual(first, 0, "idleTime is non-negative");
|
|
|
|
// Route the idle service's MOZ_LOG module to a file (its output is not
|
|
// visible to the console service); logging.config.sync flushes each line so
|
|
// the readbacks below see it. Enabled up front so it captures the poll used
|
|
// to detect the active backend.
|
|
const logBase = PathUtils.join(
|
|
Services.dirsvc.get("ProfD", Ci.nsIFile).path,
|
|
"idle_mutter_test.log"
|
|
);
|
|
const logFile = `${logBase}-main.${Services.appinfo.processID}.moz_log`;
|
|
await SpecialPowers.pushPrefEnv({
|
|
set: [
|
|
["logging.config.sync", true],
|
|
["logging.config.LOG_FILE", logBase],
|
|
["logging.nsIUserIdleService", 5],
|
|
],
|
|
});
|
|
registerCleanupFunction(() =>
|
|
IOUtils.remove(logFile, { ignoreAbsent: true })
|
|
);
|
|
|
|
async function logIncludes(needle) {
|
|
if (!(await IOUtils.exists(logFile))) {
|
|
return false;
|
|
}
|
|
return (await IOUtils.readUTF8(logFile)).includes(needle);
|
|
}
|
|
|
|
// Detect the active backend from its log: a Mutter poll logs
|
|
// "PollIdleTime() request", whereas the XScreenSaver backend logs
|
|
// "UserIdleServiceX11::PollIdleTime". One idleTime read triggers a poll.
|
|
void idle.idleTime;
|
|
await TestUtils.waitForCondition(
|
|
async () =>
|
|
(await logIncludes("PollIdleTime() request")) ||
|
|
(await logIncludes("UserIdleServiceX11")),
|
|
"idle service served a poll we can attribute to a backend",
|
|
100,
|
|
100
|
|
);
|
|
if (!(await logIncludes("PollIdleTime() request"))) {
|
|
info("Idle backend is not Mutter (XScreenSaver); skipping Mutter checks.");
|
|
return;
|
|
}
|
|
info("Mutter idle backend active; exercising the cache path.");
|
|
|
|
// Two reads within the fresh-cache window (kCacheFreshMs == 1000ms) are
|
|
// served from the cache without a new DBus poll, so they must be identical.
|
|
// This deterministically exercises the synchronous fresh-cache return path.
|
|
const a = idle.idleTime;
|
|
const b = idle.idleTime;
|
|
Assert.equal(b, a, "rapid successive reads return the same cached value");
|
|
|
|
// The fresh window must time out: once more than kCacheFreshMs has elapsed,
|
|
// the next read takes the stale branch, returns the cached value, and kicks
|
|
// off a background DBus refresh. Polling idleTime crosses that boundary
|
|
// without an arbitrary fixed delay.
|
|
await TestUtils.waitForCondition(
|
|
() => {
|
|
void idle.idleTime;
|
|
return logIncludes("returns cached (stale)");
|
|
},
|
|
"cache freshness timed out and took the stale branch",
|
|
100,
|
|
100
|
|
);
|
|
|
|
// The background refresh's async DBus reply lands and updates the cache.
|
|
await TestUtils.waitForCondition(
|
|
() => logIncludes("Async handler got"),
|
|
"background DBus refresh completed and re-cached the value",
|
|
100,
|
|
100
|
|
);
|
|
|
|
// Two paths are deliberately left untested here:
|
|
// - The cold "very stale" resync (cache older than kCacheStaleMs == 5000ms,
|
|
// which falls through to a synchronous wait). Every stale read above kicks
|
|
// a background refresh that re-stamps the cache, so reaching the very-stale
|
|
// state requires sitting >5s without touching idleTime -- i.e. an arbitrary
|
|
// fixed delay, which is both slow and flaky (see no-arbitrary-setTimeout).
|
|
// - The DBus poll timeout (kPollTimeoutMs, the "timed out" path). It only
|
|
// fires when the session bus stops replying, and there is no way to make a
|
|
// healthy Mutter IdleMonitor hang from a test.
|
|
// Both are covered by manual MOZ_LOG inspection rather than automation.
|
|
});
|