mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-06-25 17:10:43 +00:00
attempt to fix e2e
This commit is contained in:
parent
c72df2b8a5
commit
fb0008c85a
5 changed files with 238 additions and 55 deletions
|
|
@ -17,7 +17,7 @@
|
|||
"compile-config": "node ./scripts/compile_config.js",
|
||||
"build": "rolldown -c",
|
||||
"build:unit": "rolldown -c --sourcemap",
|
||||
"build:e2e": "swc src -d src-js -D --strip-leading-paths && swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths",
|
||||
"build:e2e": "rolldown -c --e2e",
|
||||
"watch:swc": "swc src -d built -D -w --strip-leading-paths",
|
||||
"build:tsc": "tsgo -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||
"watch": "pnpm compile-config && node ./scripts/watch.mjs",
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ function backendDevServerPlugin(): Plugin {
|
|||
|
||||
export default defineConfig((args) => {
|
||||
const isWatchMode = args.watch != null && args.watch !== 'false';
|
||||
const isE2E = args.e2e != null && args.e2e !== 'false';
|
||||
|
||||
// 通常のビルド時にexternalとするモジュール
|
||||
const externalModules: ExternalOption = [
|
||||
|
|
@ -75,33 +76,52 @@ export default defineConfig((args) => {
|
|||
'oauth2orize',
|
||||
];
|
||||
|
||||
return {
|
||||
input: [
|
||||
'./src/boot/entry.ts',
|
||||
'./src/boot/cli.ts',
|
||||
'./src/config.ts',
|
||||
'./src/postgres.ts',
|
||||
'./src/server/api/openapi/gen-spec.ts',
|
||||
],
|
||||
platform: 'node',
|
||||
tsconfig: true,
|
||||
plugins: [
|
||||
esmShim(),
|
||||
(isWatchMode ? backendDevServerPlugin() : undefined),
|
||||
],
|
||||
output: {
|
||||
keepNames: true,
|
||||
minify: !isWatchMode,
|
||||
sourcemap: isWatchMode,
|
||||
dir: './built',
|
||||
cleanDir: !isWatchMode,
|
||||
format: 'esm',
|
||||
},
|
||||
watch: {
|
||||
include: ['src/**/*.{ts,js,mjs,cjs,tsx,json}'],
|
||||
clearScreen: false,
|
||||
},
|
||||
// ビルドの高速化のために、watchモードのときは外部モジュールは全てバンドルしないようにする
|
||||
external: isWatchMode ? /^(?!@\/)[^.\/](?!:[\/\\])/ : externalModules,
|
||||
};
|
||||
if (isE2E) {
|
||||
return {
|
||||
input: './test-server/entry.ts',
|
||||
platform: 'node',
|
||||
tsconfig: './test-server/tsconfig.json',
|
||||
plugins: [
|
||||
esmShim(),
|
||||
],
|
||||
output: {
|
||||
keepNames: true,
|
||||
sourcemap: true,
|
||||
dir: './built-test',
|
||||
cleanDir: true,
|
||||
format: 'esm',
|
||||
},
|
||||
external: externalModules,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
input: [
|
||||
'./src/boot/entry.ts',
|
||||
'./src/boot/cli.ts',
|
||||
'./src/config.ts',
|
||||
'./src/postgres.ts',
|
||||
'./src/server/api/openapi/gen-spec.ts',
|
||||
],
|
||||
platform: 'node',
|
||||
tsconfig: true,
|
||||
plugins: [
|
||||
esmShim(),
|
||||
(isWatchMode ? backendDevServerPlugin() : undefined),
|
||||
],
|
||||
output: {
|
||||
keepNames: true,
|
||||
minify: !isWatchMode,
|
||||
sourcemap: isWatchMode,
|
||||
dir: './built',
|
||||
cleanDir: !isWatchMode,
|
||||
format: 'esm',
|
||||
},
|
||||
watch: {
|
||||
include: ['src/**/*.{ts,js,mjs,cjs,tsx,json}'],
|
||||
clearScreen: false,
|
||||
},
|
||||
// ビルドの高速化のために、watchモードのときは外部モジュールは全てバンドルしないようにする
|
||||
external: isWatchMode ? /^(?!@\/)[^.\/](?!:[\/\\])/ : externalModules,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
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 from 'fastify';
|
||||
import Fastify, { type FastifyInstance } from 'fastify';
|
||||
import { execaNode, type ResultPromise } from 'execa';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { MainModule } from '@/MainModule.js';
|
||||
import { ServerService } from '@/server/ServerService.js';
|
||||
|
|
@ -10,18 +14,27 @@ 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 launch() {
|
||||
await killTestServer();
|
||||
|
||||
async function launchApplication() {
|
||||
console.log('starting application...');
|
||||
|
||||
app = await NestFactory.createApplicationContext(MainModule, {
|
||||
|
|
@ -30,21 +43,39 @@ async function launch() {
|
|||
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 killTestServer() {
|
||||
//
|
||||
async function killServerAtPort(port: number) {
|
||||
try {
|
||||
const pid = await portToPid(config.port);
|
||||
const pid = await portToPid(port);
|
||||
if (pid) {
|
||||
await fkill(pid, { force: true });
|
||||
}
|
||||
|
|
@ -53,15 +84,44 @@ async function killTestServer() {
|
|||
}
|
||||
}
|
||||
|
||||
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 = config.port + 1000) {
|
||||
async function startControllerEndpoints(port = controllerPort) {
|
||||
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 });
|
||||
|
|
@ -75,24 +135,126 @@ async function startControllerEndpoints(port = config.port + 1000) {
|
|||
|
||||
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
||||
process.env = JSON.parse(originEnv);
|
||||
|
||||
await serverService.dispose();
|
||||
await app.close();
|
||||
|
||||
await killTestServer();
|
||||
|
||||
console.log('starting application...');
|
||||
|
||||
app = await NestFactory.createApplicationContext(MainModule, {
|
||||
logger: new NestLogger(),
|
||||
});
|
||||
serverService = app.get(ServerService);
|
||||
await serverService.launch();
|
||||
await relaunchApplication();
|
||||
|
||||
res.code(200).send({ success: true });
|
||||
});
|
||||
|
||||
fastify.post('/shutdown', async (_req, res) => {
|
||||
res.code(200).send({ success: true });
|
||||
|
||||
setImmediate(() => {
|
||||
void shutdownChildProcess().finally(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await fastify.listen({ port: port, host: 'localhost' });
|
||||
controllerServer = fastify;
|
||||
}
|
||||
|
||||
export default launch;
|
||||
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() {
|
||||
await killTestServers();
|
||||
|
||||
const child = execaNode(entryFilePath, [], {
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await waitForControllerReady();
|
||||
} catch (error) {
|
||||
terminateChild(child);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return async () => {
|
||||
try {
|
||||
await requestChildShutdown();
|
||||
} catch {
|
||||
terminateChild(child);
|
||||
}
|
||||
|
||||
await waitForChildExit(child);
|
||||
};
|
||||
}
|
||||
|
||||
if (isExecutedDirectly) {
|
||||
void runServerProcess().catch((error: unknown) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@kitajs/html",
|
||||
"rootDir": "../src",
|
||||
"paths": {
|
||||
"@/*": ["../src/*"]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ export const baseConfig = defineConfig({
|
|||
},
|
||||
restoreMocks: true,
|
||||
testTimeout: 60000,
|
||||
hookTimeout: 60000,
|
||||
teardownTimeout: 60000,
|
||||
maxWorkers: 1,
|
||||
logHeapUsage: true,
|
||||
vmMemoryLimit: 1024,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue