mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-07 19:34:17 +00:00
Make dbus module side-effect free to not interfere with unit tests
When code that is unit tested is part of a file that imports modules which depend on the dbus module, this breaks the unit test environment because there is no system socket set up, as the unit test mocha config doesn't import fixtures.ts. For example, if we change src/compose/utils to import device-config or api-binder, both of those modules import lib/dbus which invokes a dbus.getBus call at the root level. This is problematic for unit testing. We can get around the root-level dbus.getBus call by initializing dbus only when it's first needed. The mocked-dbus test setup code can also be removed in favor of legacy mocha hooks, which makes the dbus stubbing in the legacy test environment more clear. We can remove these legacy hooks when all the legacy tests are migrated to unit/integration. Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
parent
966d957465
commit
f586b7c9a8
@ -1,15 +1,27 @@
|
||||
import { getBus, Error as DBusError } from 'dbus';
|
||||
import { promisify } from 'util';
|
||||
import { TypedError } from 'typed-error';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import log from './supervisor-console';
|
||||
import DBus = require('dbus');
|
||||
|
||||
export class DbusError extends TypedError {}
|
||||
|
||||
const bus = getBus('system');
|
||||
const getInterfaceAsync = promisify(bus.getInterface.bind(bus));
|
||||
let bus: DBus.DBusConnection;
|
||||
let getInterfaceAsync: <T = DBus.AnyInterfaceMethod>(
|
||||
serviceName: string,
|
||||
objectPath: string,
|
||||
ifaceName: string,
|
||||
) => Promise<DBus.DBusInterface<T>>;
|
||||
|
||||
export const initialized = _.once(async () => {
|
||||
bus = getBus('system');
|
||||
getInterfaceAsync = promisify(bus.getInterface.bind(bus));
|
||||
});
|
||||
|
||||
async function getSystemdInterface() {
|
||||
await initialized();
|
||||
try {
|
||||
return await getInterfaceAsync(
|
||||
'org.freedesktop.systemd1',
|
||||
@ -21,7 +33,8 @@ async function getSystemdInterface() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLoginManagerInterface() {
|
||||
async function getLoginManagerInterface() {
|
||||
await initialized();
|
||||
try {
|
||||
return await getInterfaceAsync(
|
||||
'org.freedesktop.login1',
|
||||
|
@ -8,6 +8,7 @@ module.exports = {
|
||||
'tsconfig-paths/register',
|
||||
'test/lib/chai.ts',
|
||||
'test/legacy/fixtures.ts',
|
||||
'test/lib/legacy-mocha-hooks.ts'
|
||||
],
|
||||
spec: ['test/legacy/**/*.spec.ts'],
|
||||
timeout: '30000',
|
||||
|
@ -31,6 +31,5 @@ fs.writeFileSync(
|
||||
fs.readFileSync('./test/data/testconfig.json'),
|
||||
);
|
||||
|
||||
import '~/test-lib/mocked-dbus';
|
||||
import '~/test-lib/mocked-dockerode';
|
||||
import '~/test-lib/mocked-iptables';
|
||||
|
70
test/lib/legacy-mocha-hooks.ts
Normal file
70
test/lib/legacy-mocha-hooks.ts
Normal file
@ -0,0 +1,70 @@
|
||||
// TODO: Remove this file when all legacy tests have migrated to unit/integration.
|
||||
|
||||
import { stub, SinonStub } from 'sinon';
|
||||
import * as dbus from 'dbus';
|
||||
import { Error as DBusError, DBusInterface } from 'dbus';
|
||||
import { initialized } from '~/src/lib/dbus';
|
||||
|
||||
let getBusStub: SinonStub;
|
||||
|
||||
export const mochaHooks = {
|
||||
async beforeAll() {
|
||||
getBusStub = stub(dbus, 'getBus').returns({
|
||||
getInterface: (
|
||||
serviceName: string,
|
||||
_objectPath: string,
|
||||
_interfaceName: string,
|
||||
interfaceCb: (err: null | DBusError, iface: DBusInterface) => void,
|
||||
) => {
|
||||
if (/systemd/.test(serviceName)) {
|
||||
interfaceCb(null, {
|
||||
StartUnit: () => {
|
||||
// noop
|
||||
},
|
||||
RestartUnit: () => {
|
||||
// noop
|
||||
},
|
||||
StopUnit: () => {
|
||||
// noop
|
||||
},
|
||||
EnableUnitFiles: () => {
|
||||
// noop
|
||||
},
|
||||
DisableUnitFiles: () => {
|
||||
// noop
|
||||
},
|
||||
GetUnit: (
|
||||
_unitName: string,
|
||||
getUnitCb: (err: null | Error, unitPath: string) => void,
|
||||
) => {
|
||||
getUnitCb(null, 'this is the unit path');
|
||||
},
|
||||
Get: (
|
||||
_unitName: string,
|
||||
_property: string,
|
||||
getCb: (err: null | Error, value: unknown) => void,
|
||||
) => {
|
||||
getCb(null, 'this is the value');
|
||||
},
|
||||
} as any);
|
||||
} else {
|
||||
interfaceCb(null, {
|
||||
Reboot: () => {
|
||||
// noop
|
||||
},
|
||||
PowerOff: () => {
|
||||
// noop
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
},
|
||||
} as dbus.DBusConnection);
|
||||
|
||||
// Initialize dbus module before any tests are run so any further tests
|
||||
// that interface with lib/dbus use the stubbed busses above.
|
||||
await initialized();
|
||||
},
|
||||
afterAll() {
|
||||
getBusStub.restore();
|
||||
},
|
||||
};
|
@ -1,67 +0,0 @@
|
||||
import * as dbus from 'dbus';
|
||||
import { Error as DBusError, DBusInterface } from 'dbus';
|
||||
import { stub } from 'sinon';
|
||||
|
||||
/**
|
||||
* Because lib/dbus invokes dbus.getBus on module import,
|
||||
* getBus needs to be stubbed at the root level due how JS
|
||||
* `require` works. lib/dbus interfaces with the systemd and
|
||||
* logind interfaces, which expose the unit methods below.
|
||||
*
|
||||
* There should be no need to un-stub dbus.getBus at any point
|
||||
* during testing, since we never want to interact with the actual
|
||||
* dbus system socket in the test environment.
|
||||
*
|
||||
* To test interaction with lib/dbus, import lib/dbus into the test suite
|
||||
* and stub the necessary methods, as you would with any other module.
|
||||
*/
|
||||
stub(dbus, 'getBus').returns({
|
||||
getInterface: (
|
||||
serviceName: string,
|
||||
_objectPath: string,
|
||||
_interfaceName: string,
|
||||
interfaceCb: (err: null | DBusError, iface: DBusInterface) => void,
|
||||
) => {
|
||||
if (/systemd/.test(serviceName)) {
|
||||
interfaceCb(null, {
|
||||
StartUnit: () => {
|
||||
// noop
|
||||
},
|
||||
RestartUnit: () => {
|
||||
// noop
|
||||
},
|
||||
StopUnit: () => {
|
||||
// noop
|
||||
},
|
||||
EnableUnitFiles: () => {
|
||||
// noop
|
||||
},
|
||||
DisableUnitFiles: () => {
|
||||
// noop
|
||||
},
|
||||
GetUnit: (
|
||||
_unitName: string,
|
||||
getUnitCb: (err: null | Error, unitPath: string) => void,
|
||||
) => {
|
||||
getUnitCb(null, 'this is the unit path');
|
||||
},
|
||||
Get: (
|
||||
_unitName: string,
|
||||
_property: string,
|
||||
getCb: (err: null | Error, value: unknown) => void,
|
||||
) => {
|
||||
getCb(null, 'this is the value');
|
||||
},
|
||||
} as any);
|
||||
} else {
|
||||
interfaceCb(null, {
|
||||
Reboot: () => {
|
||||
// noop
|
||||
},
|
||||
PowerOff: () => {
|
||||
// noop
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
},
|
||||
} as dbus.DBusConnection);
|
Loading…
x
Reference in New Issue
Block a user