Merge pull request #14985 from backstage/mob/pls-stop
backend-app-api, backend-test-utils: added backend.stop() and auto stop in tests
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Backends started with `startTestBackend` are now automatically stopped after all tests have run.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-app-api': patch
|
||||
---
|
||||
|
||||
Added `.stop()` method to `Backend`.
|
||||
@@ -25,6 +25,8 @@ export interface Backend {
|
||||
add(feature: BackendFeature): void;
|
||||
// (undocumented)
|
||||
start(): Promise<void>;
|
||||
// (undocumented)
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
import {
|
||||
BackendFeature,
|
||||
ExtensionPoint,
|
||||
coreServices,
|
||||
ServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { BackendLifecycleImpl } from '../services/implementations/lifecycleService';
|
||||
import {
|
||||
BackendRegisterInit,
|
||||
EnumerableServiceHolder,
|
||||
@@ -173,4 +175,23 @@ export class BackendInitializer {
|
||||
|
||||
return orderedRegisterInits;
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (!this.#started) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lifecycleService = await this.#serviceHolder.get(
|
||||
coreServices.lifecycle,
|
||||
'root',
|
||||
);
|
||||
|
||||
// TODO(Rugvip): Find a better way to do this
|
||||
const lifecycle = (lifecycleService as any)?.lifecycle;
|
||||
if (lifecycle instanceof BackendLifecycleImpl) {
|
||||
await lifecycle.shutdown();
|
||||
} else {
|
||||
throw new Error('Unexpected lifecycle service implementation');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class BackstageBackend implements Backend {
|
||||
await this.#initializer.start();
|
||||
}
|
||||
|
||||
// async stop(): Promise<void> {
|
||||
// await this.#initializer.stop();
|
||||
// }
|
||||
async stop(): Promise<void> {
|
||||
await this.#initializer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { BackstageBackend } from './BackstageBackend';
|
||||
export interface Backend {
|
||||
add(feature: BackendFeature): void;
|
||||
start(): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface BackendRegisterInit {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Backend } from '@backstage/backend-app-api';
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { Knex } from 'knex';
|
||||
@@ -23,7 +24,7 @@ export function setupRequestMockHandlers(worker: {
|
||||
export function startTestBackend<
|
||||
TServices extends any[],
|
||||
TExtensionPoints extends any[],
|
||||
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<void>;
|
||||
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<Backend>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export interface TestBackendOptions<
|
||||
|
||||
@@ -19,9 +19,42 @@ import {
|
||||
createExtensionPoint,
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from './TestBackend';
|
||||
|
||||
// This bit makes sure that test backends are cleaned up properly
|
||||
let globalTestBackendHasBeenStopped = false;
|
||||
beforeAll(async () => {
|
||||
await startTestBackend({
|
||||
services: [],
|
||||
features: [
|
||||
createBackendModule({
|
||||
moduleId: 'test.module',
|
||||
pluginId: 'test',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: { lifecycle: coreServices.lifecycle },
|
||||
async init({ lifecycle }) {
|
||||
lifecycle.addShutdownHook({
|
||||
fn() {
|
||||
globalTestBackendHasBeenStopped = true;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
})(),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (!globalTestBackendHasBeenStopped) {
|
||||
throw new Error('Expected backend to have been stopped');
|
||||
}
|
||||
});
|
||||
|
||||
describe('TestBackend', () => {
|
||||
it('should get a type error if service implementation does not match', async () => {
|
||||
type Obj = { a: string; b: string };
|
||||
@@ -94,4 +127,32 @@ describe('TestBackend', () => {
|
||||
|
||||
expect(testFn).toHaveBeenCalledWith('winning');
|
||||
});
|
||||
|
||||
it('should stop the test backend', async () => {
|
||||
const shutdownSpy = jest.fn();
|
||||
|
||||
const testModule = createBackendModule({
|
||||
moduleId: 'test.module',
|
||||
pluginId: 'test',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
lifecycle: coreServices.lifecycle,
|
||||
},
|
||||
async init({ lifecycle }) {
|
||||
lifecycle.addShutdownHook({ fn: shutdownSpy });
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const backend = await startTestBackend({
|
||||
services: [],
|
||||
features: [testModule()],
|
||||
});
|
||||
|
||||
expect(shutdownSpy).not.toHaveBeenCalled();
|
||||
await backend.stop();
|
||||
expect(shutdownSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createSpecializedBackend } from '@backstage/backend-app-api';
|
||||
import {
|
||||
Backend,
|
||||
createSpecializedBackend,
|
||||
lifecycleFactory,
|
||||
loggerFactory,
|
||||
rootLoggerFactory,
|
||||
} from '@backstage/backend-app-api';
|
||||
import {
|
||||
ServiceFactory,
|
||||
ServiceRef,
|
||||
@@ -47,11 +53,19 @@ export interface TestBackendOptions<
|
||||
features?: BackendFeature[];
|
||||
}
|
||||
|
||||
const defaultServiceFactories = [
|
||||
rootLoggerFactory(),
|
||||
loggerFactory(),
|
||||
lifecycleFactory(),
|
||||
];
|
||||
|
||||
const backendInstancesToCleanUp = new Array<Backend>();
|
||||
|
||||
/** @alpha */
|
||||
export async function startTestBackend<
|
||||
TServices extends any[],
|
||||
TExtensionPoints extends any[],
|
||||
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<void> {
|
||||
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<Backend> {
|
||||
const {
|
||||
services = [],
|
||||
extensionPoints = [],
|
||||
@@ -69,22 +83,30 @@ export async function startTestBackend<
|
||||
service: ref,
|
||||
deps: {},
|
||||
factory: async () => async () => impl,
|
||||
});
|
||||
})();
|
||||
}
|
||||
return createServiceFactory({
|
||||
service: ref,
|
||||
deps: {},
|
||||
factory: async () => impl,
|
||||
});
|
||||
})();
|
||||
}
|
||||
return serviceDef as ServiceFactory;
|
||||
});
|
||||
|
||||
for (const factory of defaultServiceFactories) {
|
||||
if (!factories.some(f => f.service === factory.service)) {
|
||||
factories.push(factory);
|
||||
}
|
||||
}
|
||||
|
||||
const backend = createSpecializedBackend({
|
||||
...otherOptions,
|
||||
services: factories,
|
||||
});
|
||||
|
||||
backendInstancesToCleanUp.push(backend);
|
||||
|
||||
backend.add({
|
||||
id: `---test-extension-point-registrar`,
|
||||
register(reg) {
|
||||
@@ -101,4 +123,32 @@ export async function startTestBackend<
|
||||
}
|
||||
|
||||
await backend.start();
|
||||
|
||||
return backend;
|
||||
}
|
||||
|
||||
let registered = false;
|
||||
function registerTestHooks() {
|
||||
if (typeof afterAll !== 'function') {
|
||||
return;
|
||||
}
|
||||
if (registered) {
|
||||
return;
|
||||
}
|
||||
registered = true;
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all(
|
||||
backendInstancesToCleanUp.map(async backend => {
|
||||
try {
|
||||
await backend.stop();
|
||||
} catch (error) {
|
||||
console.error(`Failed to stop backend after tests, ${error}`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
backendInstancesToCleanUp.length = 0;
|
||||
});
|
||||
}
|
||||
|
||||
registerTestHooks();
|
||||
|
||||
Reference in New Issue
Block a user