Add unit tests for PluginManager
Reviewed By: mweststrate Differential Revision: D34307076 fbshipit-source-id: 7cb6c808bea10d15627958e8fabc02b4563183d4
This commit is contained in:
committed by
Facebook GitHub Bot
parent
d96cf8127f
commit
9e4ac0aaa9
@@ -50,7 +50,7 @@ const isExecuteMessage = (message: object): message is ExecuteMessage =>
|
||||
(message as ExecuteMessage).method === 'execute';
|
||||
|
||||
export class PluginManager {
|
||||
private readonly serverAddOns = new Map<string, ServerAddOnManager>();
|
||||
public readonly serverAddOns = new Map<string, ServerAddOnManager>();
|
||||
|
||||
constructor(private readonly flipperServer: FlipperServerForServerAddOn) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {ServerAddOnStartDetails} from 'flipper-common';
|
||||
import {loadServerAddOn} from '../loadServerAddOn';
|
||||
import {PluginManager} from '../PluginManager';
|
||||
import {ServerAddOnManager} from '../ServerAddManager';
|
||||
import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection';
|
||||
import {
|
||||
createControlledPromise,
|
||||
detailsBundled,
|
||||
detailsInstalled,
|
||||
initialOwner,
|
||||
pluginName,
|
||||
} from './utils';
|
||||
|
||||
jest.mock('../loadServerAddOn');
|
||||
const loadServerAddOnMock = loadServerAddOn as jest.Mock;
|
||||
|
||||
describe('PluginManager', () => {
|
||||
describe('server add-ons', () => {
|
||||
const startServerAddOn = async (details: ServerAddOnStartDetails) => {
|
||||
const addOnCleanupMock = jest.fn();
|
||||
const addOnMock = jest.fn().mockImplementation(() => addOnCleanupMock);
|
||||
loadServerAddOnMock.mockImplementation(() => ({
|
||||
default: addOnMock,
|
||||
}));
|
||||
|
||||
const flipperServer = {
|
||||
connect: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
exec: jest.fn(),
|
||||
close: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(0);
|
||||
expect(addOnMock).toBeCalledTimes(0);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(0);
|
||||
|
||||
const pluginManager = new PluginManager(flipperServer);
|
||||
await pluginManager.startServerAddOn(pluginName, details, initialOwner);
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(1);
|
||||
expect(loadServerAddOnMock).toBeCalledWith(pluginName, details);
|
||||
expect(addOnMock).toBeCalledTimes(1);
|
||||
expect(addOnMock).toBeCalledWith(
|
||||
expect.any(ServerAddOnModuleToDesktopConnection),
|
||||
{
|
||||
flipperServer,
|
||||
},
|
||||
);
|
||||
expect(pluginManager.serverAddOns.size).toBe(1);
|
||||
|
||||
const serverAddOn = pluginManager.serverAddOns.get(pluginName);
|
||||
expect(serverAddOn).toBeInstanceOf(ServerAddOnManager);
|
||||
expect(serverAddOn!.state.is('active')).toBeTruthy();
|
||||
|
||||
return {
|
||||
addOnCleanupMock,
|
||||
addOnMock,
|
||||
flipperServer,
|
||||
pluginManager,
|
||||
};
|
||||
};
|
||||
|
||||
describe.each([
|
||||
['bundled', detailsBundled],
|
||||
['installed', detailsInstalled],
|
||||
])('%s', (_name, details) => {
|
||||
test('stops the add-on when the initial owner is removed', async () => {
|
||||
const {pluginManager, addOnCleanupMock} = await startServerAddOn(
|
||||
details,
|
||||
);
|
||||
|
||||
const controlledP = createControlledPromise<void>();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
const stopPromise = pluginManager.stopServerAddOn(
|
||||
pluginName,
|
||||
initialOwner,
|
||||
);
|
||||
|
||||
expect(pluginManager.serverAddOns.size).toBe(1);
|
||||
|
||||
controlledP.resolve();
|
||||
await stopPromise;
|
||||
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
expect(pluginManager.serverAddOns.size).toBe(0);
|
||||
});
|
||||
|
||||
test('adds a new owner, stops the add-on when all owners are removed', async () => {
|
||||
const {pluginManager, addOnCleanupMock} = await startServerAddOn(
|
||||
details,
|
||||
);
|
||||
|
||||
const newOwner = 'luke';
|
||||
await pluginManager.startServerAddOn(pluginName, details, newOwner);
|
||||
expect(pluginManager.serverAddOns.size).toBe(1);
|
||||
|
||||
await pluginManager.stopServerAddOn(pluginName, initialOwner);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(0);
|
||||
expect(pluginManager.serverAddOns.size).toBe(1);
|
||||
|
||||
const serverAddOn = pluginManager.serverAddOns.get(pluginName)!;
|
||||
expect(serverAddOn.state.is('active')).toBeTruthy();
|
||||
|
||||
await pluginManager.stopServerAddOn(pluginName, newOwner);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
expect(pluginManager.serverAddOns.size).toBe(0);
|
||||
|
||||
await serverAddOn.state.wait('inactive');
|
||||
});
|
||||
|
||||
test('concurrent calls to startServerAddOn start a single add-on', async () => {
|
||||
const addOnCleanupMock = jest.fn();
|
||||
const controlledP = createControlledPromise<() => void>();
|
||||
const addOnMock = jest
|
||||
.fn()
|
||||
.mockImplementation(() => controlledP.promise);
|
||||
loadServerAddOnMock.mockImplementation(() => ({
|
||||
default: addOnMock,
|
||||
}));
|
||||
|
||||
const flipperServer = {
|
||||
connect: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
exec: jest.fn(),
|
||||
close: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(0);
|
||||
expect(addOnMock).toBeCalledTimes(0);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(0);
|
||||
|
||||
const pluginManager = new PluginManager(flipperServer);
|
||||
|
||||
const startP1 = pluginManager.startServerAddOn(
|
||||
pluginName,
|
||||
details,
|
||||
initialOwner,
|
||||
);
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(1);
|
||||
expect(loadServerAddOnMock).toBeCalledWith(pluginName, details);
|
||||
expect(addOnMock).toBeCalledTimes(1);
|
||||
expect(addOnMock).toBeCalledWith(
|
||||
expect.any(ServerAddOnModuleToDesktopConnection),
|
||||
{
|
||||
flipperServer,
|
||||
},
|
||||
);
|
||||
|
||||
expect(pluginManager.serverAddOns.size).toBe(1);
|
||||
|
||||
const newOwner = 'luke';
|
||||
const startP2 = pluginManager.startServerAddOn(
|
||||
pluginName,
|
||||
details,
|
||||
newOwner,
|
||||
);
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(1);
|
||||
expect(addOnMock).toBeCalledTimes(1);
|
||||
expect(pluginManager.serverAddOns.size).toBe(1);
|
||||
|
||||
controlledP.resolve(addOnCleanupMock);
|
||||
await startP1;
|
||||
await startP2;
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(1);
|
||||
expect(addOnMock).toBeCalledTimes(1);
|
||||
expect(pluginManager.serverAddOns.size).toBe(1);
|
||||
|
||||
const serverAddOn = pluginManager.serverAddOns.get(pluginName);
|
||||
expect(serverAddOn).toBeInstanceOf(ServerAddOnManager);
|
||||
expect(serverAddOn!.state.is('active')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('concurrent calls to stopServerAddOn stop add-on only once', async () => {
|
||||
const {pluginManager, addOnCleanupMock} = await startServerAddOn(
|
||||
details,
|
||||
);
|
||||
|
||||
const controlledP = createControlledPromise<void>();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
expect(addOnCleanupMock).toBeCalledTimes(0);
|
||||
|
||||
const stopP1 = pluginManager.stopServerAddOn(pluginName, initialOwner);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
|
||||
const stopP2 = pluginManager.stopServerAddOn(pluginName, initialOwner);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
|
||||
controlledP.resolve();
|
||||
await stopP1;
|
||||
await stopP2;
|
||||
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,34 +11,18 @@ import {ServerAddOnStartDetails} from 'flipper-common';
|
||||
import {loadServerAddOn} from '../loadServerAddOn';
|
||||
import {ServerAddOn} from '../ServerAddOn';
|
||||
import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection';
|
||||
import {
|
||||
createControlledPromise,
|
||||
detailsBundled,
|
||||
detailsInstalled,
|
||||
initialOwner,
|
||||
pluginName,
|
||||
} from './utils';
|
||||
|
||||
jest.mock('../loadServerAddOn');
|
||||
const loadServerAddOnMock = loadServerAddOn as jest.Mock;
|
||||
|
||||
const createControlledPromise = () => {
|
||||
let resolve!: () => void;
|
||||
let reject!: (reason: unknown) => void;
|
||||
const promise = new Promise<void>((resolveP, rejectP) => {
|
||||
resolve = resolveP;
|
||||
reject = rejectP;
|
||||
});
|
||||
return {
|
||||
promise,
|
||||
resolve,
|
||||
reject,
|
||||
};
|
||||
};
|
||||
|
||||
describe('ServerAddOn', () => {
|
||||
const pluginName = 'lightSaber';
|
||||
const initialOwner = 'yoda';
|
||||
const detailsBundled: ServerAddOnStartDetails = {
|
||||
isBundled: true,
|
||||
};
|
||||
const detailsInstalled: ServerAddOnStartDetails = {
|
||||
path: '/dagobar/',
|
||||
};
|
||||
|
||||
const startServerAddOn = async (details: ServerAddOnStartDetails) => {
|
||||
const addOnCleanupMock = jest.fn();
|
||||
const addOnMock = jest.fn().mockImplementation(() => addOnCleanupMock);
|
||||
@@ -91,7 +75,7 @@ describe('ServerAddOn', () => {
|
||||
test('stops the add-on when the initial owner is removed', async () => {
|
||||
const {serverAddOn, addOnCleanupMock} = await startServerAddOn(details);
|
||||
|
||||
const controlledP = createControlledPromise();
|
||||
const controlledP = createControlledPromise<void>();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
const removeOwnerRes = serverAddOn.removeOwner(initialOwner);
|
||||
@@ -109,7 +93,7 @@ describe('ServerAddOn', () => {
|
||||
const newOwner = 'luke';
|
||||
serverAddOn.addOwner(newOwner);
|
||||
|
||||
const controlledP = createControlledPromise();
|
||||
const controlledP = createControlledPromise<void>();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
const removeOwnerRes1 = serverAddOn.removeOwner(initialOwner);
|
||||
@@ -138,7 +122,7 @@ describe('ServerAddOn', () => {
|
||||
test('calls stop only once when removeOwner is called twice with the same owner', async () => {
|
||||
const {serverAddOn, addOnCleanupMock} = await startServerAddOn(details);
|
||||
|
||||
const controlledP = createControlledPromise();
|
||||
const controlledP = createControlledPromise<void>();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
const removeOwnerRes1 = serverAddOn.removeOwner(initialOwner);
|
||||
|
||||
33
desktop/flipper-server-core/src/plugins/__tests__/utils.tsx
Normal file
33
desktop/flipper-server-core/src/plugins/__tests__/utils.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {ServerAddOnStartDetails} from 'flipper-common';
|
||||
|
||||
export const pluginName = 'lightSaber';
|
||||
export const initialOwner = 'yoda';
|
||||
export const detailsBundled: ServerAddOnStartDetails = {
|
||||
isBundled: true,
|
||||
};
|
||||
export const detailsInstalled: ServerAddOnStartDetails = {
|
||||
path: '/dagobar/',
|
||||
};
|
||||
|
||||
export const createControlledPromise = <T,>() => {
|
||||
let resolve!: (...res: T extends void ? [] : [T]) => void;
|
||||
let reject!: (reason: unknown) => void;
|
||||
const promise = new Promise<T>((resolveP, rejectP) => {
|
||||
resolve = resolveP as typeof resolve;
|
||||
reject = rejectP;
|
||||
});
|
||||
return {
|
||||
promise,
|
||||
resolve,
|
||||
reject,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user