mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-08-08 16:26:23 +00:00
@@ -1,10 +1,6 @@
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { takeCoverage } from 'node:v8';
|
||||
import { portToPid } from 'pid-port';
|
||||
import fkill from 'fkill';
|
||||
import Fastify, { type FastifyInstance } from 'fastify';
|
||||
import { execaNode, type ResultPromise } from 'execa';
|
||||
import Fastify from 'fastify';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { MainModule } from '@/MainModule.js';
|
||||
import { ServerService } from '@/server/ServerService.js';
|
||||
@@ -14,27 +10,18 @@ import { INestApplicationContext } from '@nestjs/common';
|
||||
|
||||
const config = loadConfig();
|
||||
const originEnv = JSON.stringify(process.env);
|
||||
const entryFilePath = fileURLToPath(import.meta.url);
|
||||
const controllerPort = config.port + 1000;
|
||||
const isExecutedDirectly = process.argv[1] != null && entryFilePath === process.argv[1];
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
let app: INestApplicationContext;
|
||||
let serverService: ServerService;
|
||||
let controllerServer: FastifyInstance | null = null;
|
||||
let shutdownPromise: Promise<void> | null = null;
|
||||
|
||||
async function flushCoverage() {
|
||||
if (process.env.NODE_V8_COVERAGE) {
|
||||
takeCoverage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* テスト用のサーバインスタンスを起動する
|
||||
* テスト用のサーバインスタンスを起動する
|
||||
*/
|
||||
async function launchApplication() {
|
||||
async function launch() {
|
||||
await killTestServer();
|
||||
|
||||
console.log('starting application...');
|
||||
|
||||
app = await NestFactory.createApplicationContext(MainModule, {
|
||||
@@ -43,39 +30,21 @@ async function launchApplication() {
|
||||
serverService = app.get(ServerService);
|
||||
await serverService.launch();
|
||||
|
||||
await startControllerEndpoints();
|
||||
|
||||
// ジョブキューは必要な時にテストコード側で起動する
|
||||
// ジョブキューが動くとテスト結果の確認に支障が出ることがあるので意図的に動かさないでいる
|
||||
|
||||
console.log('application initialized.');
|
||||
}
|
||||
|
||||
async function disposeApplication() {
|
||||
await flushCoverage();
|
||||
|
||||
if (serverService) {
|
||||
await serverService.dispose();
|
||||
}
|
||||
|
||||
if (app) {
|
||||
await app.close();
|
||||
}
|
||||
// @ts-expect-error cleanup for relaunch in the same process
|
||||
app = undefined;
|
||||
// @ts-expect-error cleanup for relaunch in the same process
|
||||
serverService = undefined;
|
||||
}
|
||||
|
||||
async function relaunchApplication() {
|
||||
await disposeApplication();
|
||||
await launchApplication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 既に重複したポートで待ち受けしているサーバがある場合はkillする
|
||||
*/
|
||||
async function killServerAtPort(port: number) {
|
||||
async function killTestServer() {
|
||||
//
|
||||
try {
|
||||
const pid = await portToPid(port);
|
||||
const pid = await portToPid(config.port);
|
||||
if (pid) {
|
||||
await fkill(pid, { force: true });
|
||||
}
|
||||
@@ -84,44 +53,15 @@ async function killServerAtPort(port: number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function killTestServers() {
|
||||
await Promise.all([
|
||||
killServerAtPort(config.port),
|
||||
killServerAtPort(controllerPort),
|
||||
]);
|
||||
}
|
||||
|
||||
async function shutdownChildProcess() {
|
||||
if (shutdownPromise) {
|
||||
return shutdownPromise;
|
||||
}
|
||||
|
||||
shutdownPromise = (async () => {
|
||||
if (controllerServer) {
|
||||
await controllerServer.close();
|
||||
controllerServer = null;
|
||||
}
|
||||
|
||||
await disposeApplication();
|
||||
})().finally(() => {
|
||||
shutdownPromise = null;
|
||||
});
|
||||
|
||||
return shutdownPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 別プロセスに切り離してしまったが故に出来なくなった環境変数の書き換え等を実現するためのエンドポイントを作る
|
||||
* @param port
|
||||
*/
|
||||
async function startControllerEndpoints(port = controllerPort) {
|
||||
async function startControllerEndpoints(port = config.port + 1000) {
|
||||
const fastify = Fastify();
|
||||
|
||||
fastify.get('/healthz', async () => {
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
fastify.post<{ Body: { key?: string, value?: string } }>('/env', async (req, res) => {
|
||||
console.log(req.body);
|
||||
const key = req.body['key'];
|
||||
if (!key) {
|
||||
res.code(400).send({ success: false });
|
||||
@@ -135,129 +75,24 @@ async function startControllerEndpoints(port = controllerPort) {
|
||||
|
||||
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
||||
process.env = JSON.parse(originEnv);
|
||||
await relaunchApplication();
|
||||
|
||||
res.code(200).send({ success: true });
|
||||
});
|
||||
await serverService.dispose();
|
||||
await app.close();
|
||||
|
||||
fastify.post('/shutdown', async (_req, res) => {
|
||||
res.code(200).send({ success: true });
|
||||
await killTestServer();
|
||||
|
||||
setImmediate(() => {
|
||||
void shutdownChildProcess().finally(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
console.log('starting application...');
|
||||
|
||||
app = await NestFactory.createApplicationContext(MainModule, {
|
||||
logger: new NestLogger(),
|
||||
});
|
||||
serverService = app.get(ServerService);
|
||||
await serverService.launch();
|
||||
|
||||
res.code(200).send({ success: true });
|
||||
});
|
||||
|
||||
await fastify.listen({ port: port, host: 'localhost' });
|
||||
controllerServer = fastify;
|
||||
}
|
||||
|
||||
async function runServerProcess() {
|
||||
await killTestServers();
|
||||
|
||||
const terminate = async (signal: NodeJS.Signals) => {
|
||||
console.log(`received ${signal}, shutting down test server...`);
|
||||
await shutdownChildProcess();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
void terminate('SIGINT');
|
||||
});
|
||||
process.on('SIGTERM', () => {
|
||||
void terminate('SIGTERM');
|
||||
});
|
||||
|
||||
await launchApplication();
|
||||
await startControllerEndpoints();
|
||||
}
|
||||
|
||||
async function waitForControllerReady() {
|
||||
for (let attempt = 0; attempt < 120; attempt++) {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${controllerPort}/healthz`);
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// NOP
|
||||
}
|
||||
|
||||
await delay(500);
|
||||
}
|
||||
|
||||
throw new Error('test server did not become ready in time');
|
||||
}
|
||||
|
||||
async function requestChildShutdown() {
|
||||
const response = await fetch(`http://127.0.0.1:${controllerPort}/shutdown`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('failed to shut down test server');
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForChildExit(child: ResultPromise) {
|
||||
await child.catch(() => {
|
||||
// NOP
|
||||
});
|
||||
}
|
||||
|
||||
function terminateChild(child: ResultPromise, signal: NodeJS.Signals = 'SIGTERM') {
|
||||
child.kill(signal);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!child.killed) {
|
||||
child.kill('SIGKILL');
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
void child.finally(() => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
}
|
||||
|
||||
export default async function globalSetup() {
|
||||
console.log('Called globalSetup. Spawning test server process...');
|
||||
await killTestServers();
|
||||
|
||||
const child = execaNode(entryFilePath, [], {
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Waiting for test server to be ready...');
|
||||
try {
|
||||
await waitForControllerReady();
|
||||
} catch (error) {
|
||||
terminateChild(child);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return async () => {
|
||||
console.log('Called globalTeardown. Shutting down test server process...');
|
||||
try {
|
||||
await requestChildShutdown();
|
||||
} catch {
|
||||
terminateChild(child);
|
||||
}
|
||||
|
||||
await waitForChildExit(child);
|
||||
};
|
||||
}
|
||||
|
||||
if (isExecutedDirectly) {
|
||||
void runServerProcess().catch((error: unknown) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
export default launch;
|
||||
|
||||
@@ -13,8 +13,6 @@ export const baseConfig = defineConfig({
|
||||
},
|
||||
restoreMocks: true,
|
||||
testTimeout: 60000,
|
||||
hookTimeout: 60000,
|
||||
teardownTimeout: 60000,
|
||||
maxWorkers: 1,
|
||||
logHeapUsage: true,
|
||||
vmMemoryLimit: 1024,
|
||||
|
||||
Reference in New Issue
Block a user