api-power-monitor-spec.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // For these tests we use a fake DBus daemon to verify powerMonitor module
  2. // interaction with the system bus. This requires python-dbusmock installed and
  3. // running (with the DBUS_SYSTEM_BUS_ADDRESS environment variable set).
  4. // script/spec-runner.js will take care of spawning the fake DBus daemon and setting
  5. // DBUS_SYSTEM_BUS_ADDRESS when python-dbusmock is installed.
  6. //
  7. // See https://pypi.python.org/pypi/python-dbusmock for more information about
  8. // python-dbusmock.
  9. import { expect } from 'chai';
  10. import * as dbus from 'dbus-native';
  11. import { ifdescribe, startRemoteControlApp } from './lib/spec-helpers';
  12. import { promisify } from 'node:util';
  13. import { setTimeout } from 'node:timers/promises';
  14. describe('powerMonitor', () => {
  15. let logindMock: any, dbusMockPowerMonitor: any, getCalls: any, emitSignal: any, reset: any;
  16. ifdescribe(process.platform === 'linux' && process.env.DBUS_SYSTEM_BUS_ADDRESS != null)('when powerMonitor module is loaded with dbus mock', () => {
  17. before(async () => {
  18. const systemBus = dbus.systemBus();
  19. const loginService = systemBus.getService('org.freedesktop.login1');
  20. const getInterface = promisify(loginService.getInterface.bind(loginService));
  21. logindMock = await getInterface('/org/freedesktop/login1', 'org.freedesktop.DBus.Mock');
  22. getCalls = promisify(logindMock.GetCalls.bind(logindMock));
  23. emitSignal = promisify(logindMock.EmitSignal.bind(logindMock));
  24. reset = promisify(logindMock.Reset.bind(logindMock));
  25. });
  26. after(async () => {
  27. await reset();
  28. });
  29. function onceMethodCalled (done: () => void) {
  30. function cb () {
  31. logindMock.removeListener('MethodCalled', cb);
  32. }
  33. done();
  34. return cb;
  35. }
  36. before(done => {
  37. logindMock.on('MethodCalled', onceMethodCalled(done));
  38. // lazy load powerMonitor after we listen to MethodCalled mock signal
  39. dbusMockPowerMonitor = require('electron').powerMonitor;
  40. });
  41. it('should call Inhibit to delay suspend once a listener is added', async () => {
  42. // No calls to dbus until a listener is added
  43. {
  44. const calls = await getCalls();
  45. expect(calls).to.be.an('array').that.has.lengthOf(0);
  46. }
  47. // Add a dummy listener to engage the monitors
  48. dbusMockPowerMonitor.on('dummy-event', () => {});
  49. try {
  50. let retriesRemaining = 3;
  51. // There doesn't seem to be a way to get a notification when a call
  52. // happens, so poll `getCalls` a few times to reduce flake.
  53. let calls: any[] = [];
  54. while (retriesRemaining-- > 0) {
  55. calls = await getCalls();
  56. if (calls.length > 0) break;
  57. await setTimeout(1000);
  58. }
  59. expect(calls).to.be.an('array').that.has.lengthOf(1);
  60. expect(calls[0].slice(1)).to.deep.equal([
  61. 'Inhibit', [
  62. [[{ type: 's', child: [] }], ['sleep']],
  63. [[{ type: 's', child: [] }], ['electron']],
  64. [[{ type: 's', child: [] }], ['Application cleanup before suspend']],
  65. [[{ type: 's', child: [] }], ['delay']]
  66. ]
  67. ]);
  68. } finally {
  69. dbusMockPowerMonitor.removeAllListeners('dummy-event');
  70. }
  71. });
  72. describe('when PrepareForSleep(true) signal is sent by logind', () => {
  73. it('should emit "suspend" event', (done) => {
  74. dbusMockPowerMonitor.once('suspend', () => done());
  75. emitSignal('org.freedesktop.login1.Manager', 'PrepareForSleep',
  76. 'b', [['b', true]]);
  77. });
  78. describe('when PrepareForSleep(false) signal is sent by logind', () => {
  79. it('should emit "resume" event', done => {
  80. dbusMockPowerMonitor.once('resume', () => done());
  81. emitSignal('org.freedesktop.login1.Manager', 'PrepareForSleep',
  82. 'b', [['b', false]]);
  83. });
  84. it('should have called Inhibit again', async () => {
  85. const calls = await getCalls();
  86. expect(calls).to.be.an('array').that.has.lengthOf(2);
  87. expect(calls[1].slice(1)).to.deep.equal([
  88. 'Inhibit', [
  89. [[{ type: 's', child: [] }], ['sleep']],
  90. [[{ type: 's', child: [] }], ['electron']],
  91. [[{ type: 's', child: [] }], ['Application cleanup before suspend']],
  92. [[{ type: 's', child: [] }], ['delay']]
  93. ]
  94. ]);
  95. });
  96. });
  97. });
  98. describe('when a listener is added to shutdown event', () => {
  99. before(async () => {
  100. const calls = await getCalls();
  101. expect(calls).to.be.an('array').that.has.lengthOf(2);
  102. dbusMockPowerMonitor.once('shutdown', () => { });
  103. });
  104. it('should call Inhibit to delay shutdown', async () => {
  105. const calls = await getCalls();
  106. expect(calls).to.be.an('array').that.has.lengthOf(3);
  107. expect(calls[2].slice(1)).to.deep.equal([
  108. 'Inhibit', [
  109. [[{ type: 's', child: [] }], ['shutdown']],
  110. [[{ type: 's', child: [] }], ['electron']],
  111. [[{ type: 's', child: [] }], ['Ensure a clean shutdown']],
  112. [[{ type: 's', child: [] }], ['delay']]
  113. ]
  114. ]);
  115. });
  116. describe('when PrepareForShutdown(true) signal is sent by logind', () => {
  117. it('should emit "shutdown" event', done => {
  118. dbusMockPowerMonitor.once('shutdown', () => { done(); });
  119. emitSignal('org.freedesktop.login1.Manager', 'PrepareForShutdown',
  120. 'b', [['b', true]]);
  121. });
  122. });
  123. });
  124. });
  125. it('is usable before app ready', async () => {
  126. const remoteApp = await startRemoteControlApp(['--boot-eval=globalThis.initialValue=require("electron").powerMonitor.getSystemIdleTime()']);
  127. expect(await remoteApp.remoteEval('globalThis.initialValue')).to.be.a('number');
  128. });
  129. describe('when powerMonitor module is loaded', () => {
  130. let powerMonitor: typeof Electron.powerMonitor;
  131. before(() => {
  132. powerMonitor = require('electron').powerMonitor;
  133. });
  134. describe('powerMonitor.getSystemIdleState', () => {
  135. it('gets current system idle state', () => {
  136. // this function is not mocked out, so we can test the result's
  137. // form and type but not its value.
  138. const idleState = powerMonitor.getSystemIdleState(1);
  139. expect(idleState).to.be.a('string');
  140. const validIdleStates = ['active', 'idle', 'locked', 'unknown'];
  141. expect(validIdleStates).to.include(idleState);
  142. });
  143. it('does not accept non positive integer threshold', () => {
  144. expect(() => {
  145. powerMonitor.getSystemIdleState(-1);
  146. }).to.throw(/must be greater than 0/);
  147. expect(() => {
  148. powerMonitor.getSystemIdleState(NaN);
  149. }).to.throw(/conversion failure/);
  150. expect(() => {
  151. powerMonitor.getSystemIdleState('a' as any);
  152. }).to.throw(/conversion failure/);
  153. });
  154. });
  155. describe('powerMonitor.getSystemIdleTime', () => {
  156. it('returns current system idle time', () => {
  157. const idleTime = powerMonitor.getSystemIdleTime();
  158. expect(idleTime).to.be.at.least(0);
  159. });
  160. });
  161. describe('powerMonitor.getCurrentThermalState', () => {
  162. it('returns a valid state', () => {
  163. expect(powerMonitor.getCurrentThermalState()).to.be.oneOf(['unknown', 'nominal', 'fair', 'serious', 'critical']);
  164. });
  165. });
  166. describe('powerMonitor.onBatteryPower', () => {
  167. it('returns a boolean', () => {
  168. expect(powerMonitor.onBatteryPower).to.be.a('boolean');
  169. expect(powerMonitor.isOnBatteryPower()).to.be.a('boolean');
  170. });
  171. });
  172. });
  173. });