From b9fe64dea1a0a61461561d54cb1375339ebfa606 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 31 Dec 2024 17:31:20 +0800 Subject: [PATCH] f --- src/command.js | 116 ---------- src/commands/stop.js | 75 ------- src/commands/stop.ts | 100 +++++++++ test/fixtures/stop-timeout/app/router.js | 8 +- test/start.test.ts | 7 +- test/{stop.test.js => stop.test.ts} | 261 +++++++++++------------ test/utils.ts | 4 + 7 files changed, 235 insertions(+), 336 deletions(-) delete mode 100644 src/command.js delete mode 100644 src/commands/stop.js create mode 100644 src/commands/stop.ts rename test/{stop.test.js => stop.test.ts} (58%) diff --git a/src/command.js b/src/command.js deleted file mode 100644 index 7944ae3..0000000 --- a/src/command.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const BaseCommand = require('common-bin'); -const Logger = require('zlogger'); -const helper = require('./helper'); - -class Command extends BaseCommand { - constructor(rawArgv) { - super(rawArgv); - - Object.assign(this.helper, helper); - - this.parserOptions = { - removeAlias: true, - removeCamelCase: true, - execArgv: true, - }; - - // common-bin setter, don't care about override at sub class - // https://github.com/node-modules/common-bin/blob/master/lib/command.js#L158 - this.options = { - sourcemap: { - description: 'whether enable sourcemap support, will load `source-map-support` etc', - type: 'boolean', - alias: [ 'ts', 'typescript' ], - }, - - require: { - description: 'inject to execArgv --require', - type: 'array', - alias: 'r', - }, - }; - - this.logger = new Logger({ - prefix: '[egg-scripts] ', - time: false, - }); - } - - get context() { - const context = super.context; - const { argv, execArgvObj, cwd } = context; - - let baseDir = argv._[0] || cwd; - if (!path.isAbsolute(baseDir)) baseDir = path.join(cwd, baseDir); - const pkgFile = path.join(baseDir, 'package.json'); - if (fs.existsSync(pkgFile)) { - const pkgInfo = require(pkgFile); - const eggInfo = pkgInfo.egg; - - // read `eggScriptsConfig.require` from package.json - const eggScriptsConfig = pkgInfo.eggScriptsConfig; - let requireFiles = Array.isArray(argv.require) ? argv.require : []; - if (eggScriptsConfig && Array.isArray(eggScriptsConfig.require)) { - requireFiles = requireFiles.concat(eggScriptsConfig.require); - } - execArgvObj.require = execArgvObj.require || []; - requireFiles - .filter(injectScript => injectScript) - .forEach(injectScript => { - let requirePath = ''; - if (path.isAbsolute(injectScript) || injectScript.startsWith(`.${path.sep}`)) { - requirePath = path.resolve(baseDir, injectScript); - } else { - requirePath = injectScript; - } - execArgvObj.require.push(requirePath); - }); - - // read argv from eggScriptsConfig in package.json - if (eggScriptsConfig && typeof eggScriptsConfig === 'object') { - for (const key in pkgInfo.eggScriptsConfig) { - const v = pkgInfo.eggScriptsConfig[key]; - // like https://github.com/node-modules/common-bin/blob/master/lib/helper.js#L180 - if (key.startsWith('node-options--')) { - const newKey = key.replace('node-options--', ''); - if (execArgvObj[newKey] == null) { - execArgvObj[newKey] = v; - } - } else { - if (argv[key] == null) { - // only set if key is not pass from command line - argv[key] = v; - } - } - } - } - - // read `egg.typescript` from package.json - if (eggInfo && eggInfo.typescript && typeof argv.sourcemap === 'undefined') { - argv.sourcemap = true; - } - - delete argv.require; - } - - // execArgv - if (argv.sourcemap) { - execArgvObj.require = execArgvObj.require || []; - execArgvObj.require.push(require.resolve('source-map-support/register')); - } - - argv.sourcemap = argv.typescript = argv.ts = undefined; - - return context; - } - - exit(code) { - process.exit(code); - } -} - -module.exports = Command; diff --git a/src/commands/stop.js b/src/commands/stop.js deleted file mode 100644 index 40d4607..0000000 --- a/src/commands/stop.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -const path = require('path'); -const util = require('util'); -const sleep = require('mz-modules/sleep'); -const Command = require('../command'); -const isWin = process.platform === 'win32'; -const osRelated = { - titleTemplate: isWin ? '\\"title\\":\\"%s\\"' : '"title":"%s"', - appWorkerPath: isWin ? 'egg-cluster\\lib\\app_worker.js' : 'egg-cluster/lib/app_worker.js', - agentWorkerPath: isWin ? 'egg-cluster\\lib\\agent_worker.js' : 'egg-cluster/lib/agent_worker.js', -}; - -class StopCommand extends Command { - - constructor(rawArgv) { - super(rawArgv); - this.usage = 'Usage: egg-scripts stop [--title=example]'; - this.serverBin = path.join(__dirname, '../start-cluster'); - this.options = { - title: { - description: 'process title description, use for kill grep', - type: 'string', - }, - }; - } - - get description() { - return 'Stop server'; - } - - async run(context) { - const { argv } = context; - - this.logger.info(`stopping egg application ${argv.title ? `with --title=${argv.title}` : ''}`); - - // node /Users/tz/Workspaces/eggjs/egg-scripts/lib/start-cluster {"title":"egg-server","workers":4,"port":7001,"baseDir":"/Users/tz/Workspaces/eggjs/test/showcase","framework":"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/egg"} - let processList = await this.helper.findNodeProcess(item => { - const cmd = item.cmd; - return argv.title ? - cmd.includes('start-cluster') && cmd.includes(util.format(osRelated.titleTemplate, argv.title)) : - cmd.includes('start-cluster'); - }); - let pids = processList.map(x => x.pid); - - if (pids.length) { - this.logger.info('got master pid %j', pids); - this.helper.kill(pids); - // wait for 5s to confirm whether any worker process did not kill by master - await sleep(argv.timeout || '5s'); - } else { - this.logger.warn('can\'t detect any running egg process'); - } - - - // node --debug-port=5856 /Users/tz/Workspaces/eggjs/test/showcase/node_modules/_egg-cluster@1.8.0@egg-cluster/lib/agent_worker.js {"framework":"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/egg","baseDir":"/Users/tz/Workspaces/eggjs/test/showcase","port":7001,"workers":2,"plugins":null,"https":false,"key":"","cert":"","title":"egg-server","clusterPort":52406} - // node /Users/tz/Workspaces/eggjs/test/showcase/node_modules/_egg-cluster@1.8.0@egg-cluster/lib/app_worker.js {"framework":"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/egg","baseDir":"/Users/tz/Workspaces/eggjs/test/showcase","port":7001,"workers":2,"plugins":null,"https":false,"key":"","cert":"","title":"egg-server","clusterPort":52406} - processList = await this.helper.findNodeProcess(item => { - const cmd = item.cmd; - return argv.title ? - (cmd.includes(osRelated.appWorkerPath) || cmd.includes(osRelated.agentWorkerPath)) && cmd.includes(util.format(osRelated.titleTemplate, argv.title)) : - (cmd.includes(osRelated.appWorkerPath) || cmd.includes(osRelated.agentWorkerPath)); - }); - pids = processList.map(x => x.pid); - - if (pids.length) { - this.logger.info('got worker/agent pids %j that is not killed by master', pids); - this.helper.kill(pids, 'SIGKILL'); - } - - this.logger.info('stopped'); - } -} - -module.exports = StopCommand; diff --git a/src/commands/stop.ts b/src/commands/stop.ts new file mode 100644 index 0000000..750eca0 --- /dev/null +++ b/src/commands/stop.ts @@ -0,0 +1,100 @@ +import { debuglog, format } from 'node:util'; +import { scheduler } from 'node:timers/promises'; +import { Args, Flags } from '@oclif/core'; +import { BaseCommand } from '../baseCommand.js'; +import { isWindows, findNodeProcess, NodeProcess, kill } from '../helper.js'; + +const debug = debuglog('@eggjs/scripts/commands/stop'); + +const osRelated = { + titleTemplate: isWindows ? '\\"title\\":\\"%s\\"' : '"title":"%s"', + // node_modules/@eggjs/cluster/dist/commonjs/app_worker.js + // node_modules/@eggjs/cluster/dist/esm/app_worker.js + appWorkerPath: /@eggjs[\/\\]cluster[\/\\]dist[\/\\](commonjs|esm)[\/\\]app_worker\.js/i, + // node_modules/@eggjs/cluster/dist/commonjs/agent_worker.js + // node_modules/@eggjs/cluster/dist/esm/agent_worker.js + agentWorkerPath: /@eggjs[\/\\]cluster[\/\\]dist[\/\\](commonjs|esm)[\/\\]agent_worker\.js/i, +}; + +export default class Stop extends BaseCommand { + static override description = 'Stop server'; + + static override examples = [ + '<%= config.bin %> <%= command.id %>', + ]; + + static override args = { + baseDir: Args.string({ + description: 'directory of application', + required: false, + }), + }; + + static override flags = { + title: Flags.string({ + description: 'process title description, use for kill grep', + }), + timeout: Flags.integer({ + description: 'the maximum timeout(ms) when app stop', + default: 5000, + }), + }; + + public async run(): Promise { + const { flags } = this; + + this.log(`stopping egg application${flags.title ? ` with --title=${flags.title}` : ''}`); + + // node ~/eggjs/scripts/scripts/start-cluster.cjs {"title":"egg-server","workers":4,"port":7001,"baseDir":"~/eggjs/test/showcase","framework":"~/eggjs/test/showcase/node_modules/egg"} + let processList = await this.findNodeProcesses(item => { + const cmd = item.cmd; + const matched = flags.title ? + cmd.includes('start-cluster') && cmd.includes(format(osRelated.titleTemplate, flags.title)) : + cmd.includes('start-cluster'); + if (matched) { + debug('find master process: %o', item); + } + return matched; + }); + let pids = processList.map(x => x.pid); + + if (pids.length) { + this.log('got master pid %j', pids); + this.killProcesses(pids); + // wait for 5s to confirm whether any worker process did not kill by master + await scheduler.wait(flags.timeout); + } else { + this.logToStderr('can\'t detect any running egg process'); + } + + // node --debug-port=5856 /Users/tz/Workspaces/eggjs/test/showcase/node_modules/_egg-cluster@1.8.0@egg-cluster/lib/agent_worker.js {"framework":"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/egg","baseDir":"/Users/tz/Workspaces/eggjs/test/showcase","port":7001,"workers":2,"plugins":null,"https":false,"key":"","cert":"","title":"egg-server","clusterPort":52406} + // node /Users/tz/Workspaces/eggjs/test/showcase/node_modules/_egg-cluster@1.8.0@egg-cluster/lib/app_worker.js {"framework":"/Users/tz/Workspaces/eggjs/test/showcase/node_modules/egg","baseDir":"/Users/tz/Workspaces/eggjs/test/showcase","port":7001,"workers":2,"plugins":null,"https":false,"key":"","cert":"","title":"egg-server","clusterPort":52406} + // ~/bin/node --no-deprecation --trace-warnings ~/eggjs/examples/helloworld/node_modules/@eggjs/cluster/dist/commonjs/agent_worker.js {"baseDir":"~/eggjs/examples/helloworld","startMode":"process","framework":"~/eggjs/examples/helloworld/node_modules/egg","title":"egg-server-helloworld","workers":10,"clusterPort":58977} + processList = await this.findNodeProcesses(item => { + const cmd = item.cmd; + const matched = flags.title ? + (osRelated.appWorkerPath.test(cmd) || osRelated.agentWorkerPath.test(cmd)) && cmd.includes(format(osRelated.titleTemplate, flags.title)) : + (osRelated.appWorkerPath.test(cmd) || osRelated.agentWorkerPath.test(cmd)); + if (matched) { + debug('find app/agent worker process: %o', item); + } + return matched; + }); + pids = processList.map(x => x.pid); + + if (pids.length) { + this.log('got worker/agent pids %j that is not killed by master', pids); + this.killProcesses(pids); + } + + this.log('stopped'); + } + + protected async findNodeProcesses(filter: (item: NodeProcess) => boolean): Promise { + return findNodeProcess(filter); + } + + protected killProcesses(pids: number[], signal: NodeJS.Signals = 'SIGTERM') { + kill(pids, signal); + } +} diff --git a/test/fixtures/stop-timeout/app/router.js b/test/fixtures/stop-timeout/app/router.js index cd29c09..2ca255f 100644 --- a/test/fixtures/stop-timeout/app/router.js +++ b/test/fixtures/stop-timeout/app/router.js @@ -1,15 +1,13 @@ -'use strict'; - module.exports = app => { - app.get('/', function* () { + app.get('/', async function() { this.body = `hi, ${app.config.framework || 'egg'}`; }); - app.get('/env', function* () { + app.get('/env', async function() { this.body = app.config.env + ', ' + app.config.pre; }); - app.get('/path', function* () { + app.get('/path', async function() { this.body = process.env.PATH; }); }; diff --git a/test/start.test.ts b/test/start.test.ts index 80fe67b..0886ebc 100644 --- a/test/start.test.ts +++ b/test/start.test.ts @@ -3,22 +3,19 @@ import { fileURLToPath } from 'node:url'; import { strict as assert } from 'node:assert'; import fs from 'node:fs/promises'; import { scheduler } from 'node:timers/promises'; -import { ChildProcess } from 'node:child_process'; import { createServer } from 'node:http'; import { once } from 'node:events'; -import coffee, { Coffee as _Coffee } from 'coffee'; +import coffee from 'coffee'; import { request } from 'urllib'; import { mm, restore } from 'mm'; import { exists } from 'utility'; -import { cleanup, replaceWeakRefMessage } from './utils.js'; +import { cleanup, replaceWeakRefMessage, Coffee } from './utils.js'; import { isWindows, getSourceFilename } from '../src/helper.js'; const version = parseInt(process.version.split('.')[0].substring(1)); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -type Coffee = _Coffee & { proc: ChildProcess, stderr: string, stdout: string, code?: number }; - describe('test/start.test.ts', () => { const eggBin = getSourceFilename('../bin/run.js'); const fixturePath = path.join(__dirname, 'fixtures/example'); diff --git a/test/stop.test.js b/test/stop.test.ts similarity index 58% rename from test/stop.test.js rename to test/stop.test.ts index 2bdd29f..7176ace 100644 --- a/test/stop.test.js +++ b/test/stop.test.ts @@ -1,113 +1,110 @@ -'use strict'; - -const path = require('path'); -const assert = require('assert'); -const fs = require('mz/fs'); -const sleep = require('mz-modules/sleep'); -const rimraf = require('mz-modules/rimraf'); -const mkdirp = require('mz-modules/mkdirp'); -const coffee = require('coffee'); -const httpclient = require('urllib'); -const mm = require('mm'); -const utils = require('./utils'); - -describe('test/stop.test.js', () => { - const eggBin = require.resolve('../bin/egg-scripts.js'); +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { strict as assert } from 'node:assert'; +import fs from 'node:fs/promises'; +import { scheduler } from 'node:timers/promises'; +import coffee from 'coffee'; +import { request } from 'urllib'; +import { mm, restore } from 'mm'; +import { cleanup, replaceWeakRefMessage, Coffee } from './utils.js'; +import { isWindows, getSourceFilename } from '../src/helper.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +describe('test/stop.test.ts', () => { + const eggBin = getSourceFilename('../bin/run.js'); const fixturePath = path.join(__dirname, 'fixtures/example'); const timeoutPath = path.join(__dirname, 'fixtures/stop-timeout'); const homePath = path.join(__dirname, 'fixtures/home'); const logDir = path.join(homePath, 'logs'); - const waitTime = '15s'; + const waitTime = 3000; before(async () => { - await mkdirp(homePath); + await fs.mkdir(homePath, { recursive: true }); }); after(async () => { - await rimraf(homePath); + await fs.rm(homePath, { force: true, recursive: true }); }); beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath)); - afterEach(() => mm.restore); + afterEach(restore); describe('stop without daemon', () => { - let app; - let killer; + let app: Coffee; + let killer: Coffee; beforeEach(async () => { - await utils.cleanup(fixturePath); - app = coffee.fork(eggBin, [ 'start', '--workers=2', fixturePath ]); + await cleanup(fixturePath); + app = coffee.fork(eggBin, [ 'start', '--workers=2', fixturePath ]) as Coffee; // app.debug(); app.expect('code', 0); - await sleep(waitTime); + await scheduler.wait(waitTime); - assert.equal(utils.replaceWeakRefMessage(app.stderr), ''); + assert.equal(replaceWeakRefMessage(app.stderr), ''); assert(app.stdout.match(/custom-framework started on http:\/\/127\.0\.0\.1:7001/)); - const result = await httpclient.request('http://127.0.0.1:7001'); - assert(result.data.toString() === 'hi, egg'); + const result = await request('http://127.0.0.1:7001'); + assert.equal(result.data.toString(), 'hi, egg'); }); afterEach(async () => { app.proc.kill('SIGTERM'); - await utils.cleanup(fixturePath); + await cleanup(fixturePath); }); it('should stop', async () => { - killer = coffee.fork(eggBin, [ 'stop', fixturePath ]); - killer.debug(); + killer = coffee.fork(eggBin, [ 'stop', fixturePath ]) as Coffee; + // killer.debug(); killer.expect('code', 0); - - // await killer.end(); - await sleep(waitTime); + await killer.end(); // make sure is kill not auto exist - assert(!app.stdout.includes('exist by env')); + assert.doesNotMatch(app.stdout, /exist by env/); // no way to handle the SIGTERM signal in windows ? - if (!isWin) { - assert(app.stdout.includes('[master] master is killed by signal SIGTERM, closing')); - assert(app.stdout.includes('[master] exit with code:0')); - assert(app.stdout.includes('[app_worker] exit with code:0')); + if (!isWindows) { + assert.match(app.stdout, /\[master] master is killed by signal SIGTERM, closing/); + assert.match(app.stdout, /\[master] exit with code:0/); + assert.match(app.stdout, /\[app_worker] exit with code:0/); // assert(app.stdout.includes('[agent_worker] exit with code:0')); } - assert(killer.stdout.includes('[egg-scripts] stopping egg application')); - assert(killer.stdout.match(/got master pid \["\d+\"\]/i)); + assert.match(killer.stdout, /stopping egg application/); + assert.match(killer.stdout, /got master pid \[\d+\]/); }); }); describe('stop with daemon', () => { beforeEach(async () => { - await utils.cleanup(fixturePath); - await rimraf(logDir); + await cleanup(fixturePath); + await fs.rm(logDir, { force: true, recursive: true }); await coffee.fork(eggBin, [ 'start', '--daemon', '--workers=2', fixturePath ]) - .debug() + // .debug() .expect('code', 0) .end(); - const result = await httpclient.request('http://127.0.0.1:7001'); + const result = await request('http://127.0.0.1:7001'); assert(result.data.toString() === 'hi, egg'); }); afterEach(async () => { - await utils.cleanup(fixturePath); + await cleanup(fixturePath); }); it('should stop', async () => { await coffee.fork(eggBin, [ 'stop', fixturePath ]) .debug() - .expect('stdout', /\[egg-scripts] stopping egg application/) - .expect('stdout', /got master pid \["\d+\"\]/i) + .expect('stdout', /stopping egg application/) + .expect('stdout', /got master pid \[\d+\]/i) .expect('code', 0) .end(); - await sleep(waitTime); - // master log const stdout = await fs.readFile(path.join(logDir, 'master-stdout.log'), 'utf-8'); // no way to handle the SIGTERM signal in windows ? - if (!isWin) { - assert(stdout.includes('[master] master is killed by signal SIGTERM, closing')); - assert(stdout.includes('[master] exit with code:0')); - assert(stdout.includes('[app_worker] exit with code:0')); + if (!isWindows) { + assert.match(stdout, /\[master] master is killed by signal SIGTERM, closing/); + assert.match(stdout, /\[master] exit with code:0/); + assert.match(stdout, /\[app_worker] exit with code:0/); } await coffee.fork(eggBin, [ 'stop', fixturePath ]) @@ -120,10 +117,10 @@ describe('test/stop.test.js', () => { describe('stop with not exist', () => { it('should work', async () => { - await utils.cleanup(fixturePath); + await cleanup(fixturePath); await coffee.fork(eggBin, [ 'stop', fixturePath ]) - .debug() - .expect('stdout', /\[egg-scripts] stopping egg application/) + // .debug() + .expect('stdout', /stopping egg application/) .expect('stderr', /can't detect any running egg process/) .expect('code', 0) .end(); @@ -131,41 +128,41 @@ describe('test/stop.test.js', () => { }); describe('stop --title', () => { - let app; - let killer; + let app: Coffee; + let killer: Coffee; beforeEach(async () => { - await utils.cleanup(fixturePath); - app = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=example', fixturePath ]); + await cleanup(fixturePath); + app = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=example', fixturePath ]) as Coffee; // app.debug(); app.expect('code', 0); - await sleep(waitTime); + await scheduler.wait(waitTime); - assert.equal(utils.replaceWeakRefMessage(app.stderr), ''); - assert(app.stdout.match(/custom-framework started on http:\/\/127\.0\.0\.1:7001/)); - const result = await httpclient.request('http://127.0.0.1:7001'); + assert.equal(replaceWeakRefMessage(app.stderr), ''); + assert.match(app.stdout, /custom-framework started on http:\/\/127\.0\.0\.1:7001/); + const result = await request('http://127.0.0.1:7001'); assert(result.data.toString() === 'hi, egg'); }); afterEach(async () => { app.proc.kill('SIGTERM'); - await utils.cleanup(fixturePath); + await cleanup(fixturePath); }); - it('shoud stop only if the title matches exactly', async () => { + it('should stop only if the title matches exactly', async () => { // Because of'exmaple'.inclues('exmap') === true,if egg-scripts <= 2.1.0 and you run `.. stop --title=exmap`,the process with 'title:example' will also be killed unexpectedly await coffee.fork(eggBin, [ 'stop', '--title=examp', fixturePath ]) - .debug() - .expect('stdout', /\[egg-scripts] stopping egg application with --title=examp/) + // .debug() + .expect('stdout', /stopping egg application with --title=examp/) .expect('stderr', /can't detect any running egg process/) .expect('code', 0) .end(); // stop only if the title matches exactly await coffee.fork(eggBin, [ 'stop', '--title=example', fixturePath ]) - .debug() - .expect('stdout', /\[egg-scripts] stopping egg application with --title=example/) - .expect('stdout', /\[egg-scripts\] got master pid \[/) + // .debug() + .expect('stdout', /stopping egg application with --title=example/) + .expect('stdout', /got master pid \[/) .expect('code', 0) .end(); }); @@ -173,93 +170,89 @@ describe('test/stop.test.js', () => { it('should stop', async () => { await coffee.fork(eggBin, [ 'stop', '--title=random', fixturePath ]) .debug() - .expect('stdout', /\[egg-scripts] stopping egg application with --title=random/) + .expect('stdout', /stopping egg application with --title=random/) .expect('stderr', /can't detect any running egg process/) .expect('code', 0) .end(); - killer = coffee.fork(eggBin, [ 'stop', '--title=example' ], { cwd: fixturePath }); + killer = coffee.fork(eggBin, [ 'stop', '--title=example' ], { cwd: fixturePath }) as Coffee; killer.debug(); - killer.expect('code', 0); - - // await killer.end(); - await sleep(waitTime); + // killer.expect('code', 0); + await killer.end(); // make sure is kill not auto exist - assert(!app.stdout.includes('exist by env')); + assert.doesNotMatch(app.stdout, /exist by env/); // no way to handle the SIGTERM signal in windows ? - if (!isWin) { + if (!isWindows) { assert(app.stdout.includes('[master] master is killed by signal SIGTERM, closing')); assert(app.stdout.includes('[master] exit with code:0')); assert(app.stdout.includes('[app_worker] exit with code:0')); // assert(app.stdout.includes('[agent_worker] exit with code:0')); } - assert(killer.stdout.includes('[egg-scripts] stopping egg application with --title=example')); - assert(killer.stdout.match(/got master pid \["\d+\"\]/i)); + assert(killer.stdout.includes('stopping egg application with --title=example')); + assert(killer.stdout.match(/got master pid \[\d+\]/i)); }); }); describe('stop all', () => { - let app; - let app2; - let killer; + let app: Coffee; + let app2: Coffee; + let killer: Coffee; beforeEach(async () => { - await utils.cleanup(fixturePath); - app = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=example', fixturePath ]); + await cleanup(fixturePath); + app = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=example', fixturePath ]) as Coffee; // app.debug(); app.expect('code', 0); - app2 = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=test', '--port=7002', fixturePath ]); + app2 = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=test', '--port=7002', fixturePath ]) as Coffee; app2.expect('code', 0); - await sleep(waitTime); + await scheduler.wait(waitTime); - assert.equal(utils.replaceWeakRefMessage(app.stderr), ''); + assert.equal(replaceWeakRefMessage(app.stderr), ''); assert(app.stdout.match(/custom-framework started on http:\/\/127\.0\.0\.1:7001/)); - const result = await httpclient.request('http://127.0.0.1:7001'); + const result = await request('http://127.0.0.1:7001'); assert(result.data.toString() === 'hi, egg'); - assert.equal(utils.replaceWeakRefMessage(app2.stderr), ''); + assert.equal(replaceWeakRefMessage(app2.stderr), ''); assert(app2.stdout.match(/custom-framework started on http:\/\/127\.0\.0\.1:7002/)); - const result2 = await httpclient.request('http://127.0.0.1:7002'); + const result2 = await request('http://127.0.0.1:7002'); assert(result2.data.toString() === 'hi, egg'); }); afterEach(async () => { app.proc.kill('SIGTERM'); app2.proc.kill('SIGTERM'); - await utils.cleanup(fixturePath); + await cleanup(fixturePath); }); it('should stop', async () => { - killer = coffee.fork(eggBin, [ 'stop' ], { cwd: fixturePath }); + killer = coffee.fork(eggBin, [ 'stop' ], { cwd: fixturePath }) as Coffee; killer.debug(); - killer.expect('code', 0); - - // await killer.end(); - await sleep(waitTime); + // killer.expect('code', 0); + await killer.end(); // make sure is kill not auto exist assert(!app.stdout.includes('exist by env')); // no way to handle the SIGTERM signal in windows ? - if (!isWin) { + if (!isWindows) { assert(app.stdout.includes('[master] master is killed by signal SIGTERM, closing')); assert(app.stdout.includes('[master] exit with code:0')); assert(app.stdout.includes('[app_worker] exit with code:0')); // assert(app.stdout.includes('[agent_worker] exit with code:0')); } - assert(killer.stdout.includes('[egg-scripts] stopping egg application')); - assert(killer.stdout.match(/got master pid \["\d+\","\d+\"\]/i)); + assert(killer.stdout.includes('stopping egg application')); + assert(killer.stdout.match(/got master pid \[\d+,\d+\]/i)); assert(!app2.stdout.includes('exist by env')); // no way to handle the SIGTERM signal in windows ? - if (!isWin) { + if (!isWindows) { assert(app2.stdout.includes('[master] master is killed by signal SIGTERM, closing')); assert(app2.stdout.includes('[master] exit with code:0')); assert(app2.stdout.includes('[app_worker] exit with code:0')); @@ -268,77 +261,76 @@ describe('test/stop.test.js', () => { }); describe('stop all with timeout', function() { - let app; - let killer; + let app: Coffee; + let killer: Coffee; this.timeout(17000); beforeEach(async () => { - await utils.cleanup(timeoutPath); - app = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=stop-timeout', timeoutPath ]); - app.debug(); + await cleanup(timeoutPath); + app = coffee.fork(eggBin, [ 'start', '--workers=2', '--title=stop-timeout', timeoutPath ]) as Coffee; + // app.debug(); app.expect('code', 0); - await sleep(waitTime); + await scheduler.wait(waitTime); - assert.equal(utils.replaceWeakRefMessage(app.stderr), ''); + // assert.equal(replaceWeakRefMessage(app.stderr), ''); assert(app.stdout.match(/http:\/\/127\.0\.0\.1:7001/)); - const result = await httpclient.request('http://127.0.0.1:7001'); + const result = await request('http://127.0.0.1:7001'); assert(result.data.toString() === 'hi, egg'); }); afterEach(async () => { app.proc.kill('SIGTERM'); - await utils.cleanup(timeoutPath); + await cleanup(timeoutPath); }); it('should stop error without timeout', async () => { - killer = coffee.fork(eggBin, [ 'stop' ], { cwd: timeoutPath }); + killer = coffee.fork(eggBin, [ 'stop' ], { cwd: timeoutPath }) as Coffee; killer.debug(); killer.expect('code', 0); - - // await killer.end(); - await sleep(waitTime); + await killer.end(); + await scheduler.wait(waitTime); // make sure is kill not auto exist assert(!app.stdout.includes('exist by env')); // no way to handle the SIGTERM signal in windows ? - if (!isWin) { + if (!isWindows) { assert(app.stdout.includes('[master] master is killed by signal SIGTERM, closing')); assert(app.stdout.match(/app_worker#\d+:\d+ disconnect/)); assert(app.stdout.match(/don't fork, because worker:\d+ will be kill soon/)); } - assert(killer.stdout.includes('[egg-scripts] stopping egg application')); - assert(killer.stdout.match(/got master pid \["\d+\"]/i)); + assert(killer.stdout.includes('stopping egg application')); + assert(killer.stdout.match(/got master pid \[\d+\]/i)); }); it('should stop success', async () => { - killer = coffee.fork(eggBin, [ 'stop', '--timeout=10s' ], { cwd: timeoutPath }); + killer = coffee.fork(eggBin, [ 'stop', '--timeout=10000' ], { cwd: timeoutPath }) as Coffee; killer.debug(); killer.expect('code', 0); // await killer.end(); - await sleep(waitTime); + await scheduler.wait(waitTime); // make sure is kill not auto exist assert(!app.stdout.includes('exist by env')); // no way to handle the SIGTERM signal in windows ? - if (!isWin) { + if (!isWindows) { assert(app.stdout.includes('[master] master is killed by signal SIGTERM, closing')); assert(app.stdout.includes('[master] exit with code:0')); assert(app.stdout.includes('[agent_worker] exit with code:0')); } - assert(killer.stdout.includes('[egg-scripts] stopping egg application')); - assert(killer.stdout.match(/got master pid \["\d+\"]/i)); + assert(killer.stdout.includes('stopping egg application')); + assert(killer.stdout.match(/got master pid \[\d+\]/i)); }); }); describe('stop with symlink', () => { const baseDir = path.join(__dirname, 'fixtures/tmp'); - beforeEach(async () => { + beforeEach(async function() { // if we can't create a symlink, skip the test try { await fs.symlink(fixturePath, baseDir, 'dir'); @@ -349,36 +341,35 @@ describe('test/stop.test.js', () => { } // *unix get the real path of symlink, but windows wouldn't - const appPathInRegexp = isWin ? baseDir.replace(/\\/g, '\\\\') : fixturePath; + const appPathInRegexp = isWindows ? baseDir.replace(/\\/g, '\\\\') : fixturePath; - await utils.cleanup(fixturePath); - await rimraf(logDir); + await cleanup(fixturePath); + await fs.rm(logDir, { force: true, recursive: true }); await coffee.fork(eggBin, [ 'start', '--daemon', '--workers=2' ], { cwd: baseDir }) .debug() .expect('stdout', new RegExp(`Starting custom-framework application at ${appPathInRegexp}`)) .expect('code', 0) .end(); - await rimraf(baseDir); - const result = await httpclient.request('http://127.0.0.1:7001'); + await fs.rm(baseDir, { force: true, recursive: true }); + const result = await request('http://127.0.0.1:7001'); assert(result.data.toString() === 'hi, egg'); }); afterEach(async () => { - await utils.cleanup(fixturePath); - await rimraf(baseDir); + await cleanup(fixturePath); + await fs.rm(baseDir, { force: true, recursive: true }); }); it('should stop', async () => { - await rimraf(baseDir); + await fs.rm(baseDir, { force: true, recursive: true }); await fs.symlink(path.join(__dirname, 'fixtures/status'), baseDir); await coffee.fork(eggBin, [ 'stop', baseDir ]) .debug() - .expect('stdout', /\[egg-scripts] stopping egg application/) - .expect('stdout', /got master pid \["\d+\"\]/i) + .expect('stdout', /stopping egg application/) + .expect('stdout', /got master pid \[\d+\]/i) .expect('code', 0) .end(); }); }); - }); diff --git a/test/utils.ts b/test/utils.ts index 078656d..60f1673 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,6 +1,10 @@ import { scheduler } from 'node:timers/promises'; +import { ChildProcess } from 'node:child_process'; +import { Coffee as _Coffee } from 'coffee'; import { isWindows, findNodeProcess } from '../src/helper.js'; +export type Coffee = _Coffee & { proc: ChildProcess, stderr: string, stdout: string, code?: number }; + export async function cleanup(baseDir: string) { const processList = await findNodeProcess(x => { const dir = isWindows ? baseDir.replace(/\\/g, '\\\\') : baseDir;