diff --git a/lib/cmd/start.js b/lib/cmd/start.js index 6286043..dd932ae 100644 --- a/lib/cmd/start.js +++ b/lib/cmd/start.js @@ -209,9 +209,17 @@ class StartCommand extends Command { if (hasError) { try { - const [ stdout ] = yield exec('tail -n 100 ' + stderr); - this.logger.error('Got error when startup: '); - this.logger.error(stdout); + if (process.platform !== 'win32') { + const [ stdout ] = yield exec('tail -n 100 ' + stderr); + this.logger.error('Got error when startup: '); + this.logger.error(stdout); + } else { + const str = yield fs.readFile(stderr, 'utf-8'); + let stdout = str ? str.slice(0, 50000).replace(/\r/g, '\n') : ''; + stdout = stdout.split(/\n+/g).splice(0, 100).join('\n'); + this.logger.error('Got error when startup: '); + this.logger.error(stdout); + } } catch (_) { // nothing } diff --git a/lib/cmd/stop.js b/lib/cmd/stop.js index 09585c8..f9f96ac 100644 --- a/lib/cmd/stop.js +++ b/lib/cmd/stop.js @@ -23,13 +23,8 @@ class StopCommand extends Command { } * run(context) { - /* istanbul ignore next */ - if (process.platform === 'win32') { - this.logger.warn('Windows is not supported, try to kill master process which command contains `start-cluster` or `--type=egg-server` yourself, good luck.'); - process.exit(0); - } - const { argv } = context; + const port = argv.port || (process.env && process.env.PORT); this.logger.info(`stopping egg application ${argv.title ? `with --title=${argv.title}` : ''}`); @@ -39,7 +34,7 @@ class StopCommand extends Command { return argv.title ? cmd.includes('start-cluster') && cmd.includes(`"title":"${argv.title}"`) : cmd.includes('start-cluster'); - }); + }, port); let pids = processList.map(x => x.pid); if (pids.length) { @@ -59,7 +54,7 @@ class StopCommand extends Command { return argv.title ? (cmd.includes('egg-cluster/lib/app_worker.js') || cmd.includes('egg-cluster/lib/agent_worker.js')) && cmd.includes(`"title":"${argv.title}"`) : (cmd.includes('egg-cluster/lib/app_worker.js') || cmd.includes('egg-cluster/lib/agent_worker.js')); - }); + }, port); pids = processList.map(x => x.pid); if (pids.length) { diff --git a/lib/helper.js b/lib/helper.js index 881d869..8d7c356 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -3,7 +3,11 @@ const runScript = require('runscript'); const REGEX = /^\s*(\d+)\s+(.*)/; -exports.findNodeProcess = function* (filterFn) { +exports.findNodeProcess = function* (filterFn, port) { + if (process.platform === 'win32') { + return yield findNodeProcessWin(port); + } + const command = 'ps -eo "pid,command"'; const stdio = yield runScript(command, { stdio: 'pipe' }); const processList = stdio.stdout.toString().split('\n') @@ -21,12 +25,56 @@ exports.findNodeProcess = function* (filterFn) { return arr; }, []); return processList; + }; +function* findNodeProcessWin(port) { + port = +port; + if (!Number.isSafeInteger(port)) { + return []; + } + const command = `netstat -aon|findstr ":${port}"`; + let stdio; + + try { + stdio = yield runScript(command, { stdio: 'pipe' }); + } catch (ex) { + return []; + } + const map = new Map(); + + stdio.stdout.toString().split('\n') + .forEach(line => { + if (line) { + // [ '', 'TCP', '0.0.0.0:7001', '0.0.0.0:0', 'LISTENING', '4580', '' ] + // [ '', 'TCP', '[::]:7001', '0.0.0.0:0', 'LISTENING', '4580', '' ] + const lineArr = line.split(/\s+/); + + if (!lineArr[0] && lineArr[1] === 'TCP' && lineArr[2] && lineArr[5]) { + const pid = lineArr[5]; + const ipArr = lineArr[2].split(':'); + + if (!Number.isSafeInteger(+pid) && map.has(pid)) { + return; + } + + if (ipArr && ipArr.length >= 2) { + if (+ipArr[ipArr.length - 1] === port) { // ipv4/v6 + map.set(pid, { pid, cmd: '' }); + } + } + } + } + }); + + return Array.from(map.values()); +} +exports.findNodeProcessWin = findNodeProcessWin; + exports.kill = function(pids, signal) { pids.forEach(pid => { try { - process.kill(pid, signal); + process.kill(pid, 0) && process.kill(pid, signal); } catch (err) { /* istanbul ignore next */ if (err.code !== 'ESRCH') { throw err; diff --git a/test/start.test.js b/test/start.test.js index 9ef961e..82cdb14 100644 --- a/test/start.test.js +++ b/test/start.test.js @@ -175,7 +175,7 @@ describe('test/start.test.js', () => { after(function* () { app.proc.kill('SIGTERM'); - yield utils.cleanup(fixturePath); + yield utils.cleanup(fixturePath, 7002); }); it('should start', function* () { @@ -201,7 +201,7 @@ describe('test/start.test.js', () => { after(function* () { app.proc.kill('SIGTERM'); - yield utils.cleanup(fixturePath); + yield utils.cleanup(fixturePath, 7002); }); it('should start', function* () { @@ -271,7 +271,8 @@ describe('test/start.test.js', () => { let result = yield httpclient.request('http://127.0.0.1:7001/env'); assert(result.data.toString() === 'pre, true'); result = yield httpclient.request('http://127.0.0.1:7001/path'); - assert(result.data.toString().match(new RegExp(`^${fixturePath}/node_modules/.bin${path.delimiter}`))); + const p = path.normalize(`${fixturePath}/node_modules/.bin${path.delimiter}`).replace(/\\/g, '\\\\'); + assert(result.data.toString().match(new RegExp(`^${p}`))); }); }); @@ -318,7 +319,7 @@ describe('test/start.test.js', () => { after(function* () { app.proc.kill('SIGTERM'); - yield utils.cleanup(fixturePath); + yield utils.cleanup(fixturePath, 8000); }); it('should start', function* () { @@ -411,6 +412,7 @@ describe('test/start.test.js', () => { .debug() .end(); yield utils.cleanup(cwd); + yield utils.cleanup(cwd, 7002); }); it('should start custom-framework', function* () { @@ -486,7 +488,7 @@ describe('test/start.test.js', () => { mm(process.env, 'WAIT_TIME', 5000); mm(process.env, 'ERROR', 'error message'); - const stderr = path.join(homePath, 'logs/master-stderr.log'); + const stderr = path.join(homePath, 'logs/master-stderr.log').replace(/\\/g, '\\\\'); yield coffee.fork(eggBin, [ 'start', '--daemon', '--workers=1' ], { cwd }) // .debug() @@ -509,3 +511,4 @@ describe('test/start.test.js', () => { }); }); + diff --git a/test/stop.test.js b/test/stop.test.js index 1ea012e..2dff037 100644 --- a/test/stop.test.js +++ b/test/stop.test.js @@ -10,6 +10,7 @@ const coffee = require('coffee'); const httpclient = require('urllib'); const mm = require('mm'); const utils = require('./utils'); +const helper = require('../lib/helper'); describe('test/stop.test.js', () => { const eggBin = require.resolve('../bin/egg-scripts.js'); @@ -17,6 +18,7 @@ describe('test/stop.test.js', () => { const homePath = path.join(__dirname, 'fixtures/home'); const logDir = path.join(homePath, 'logs'); const waitTime = '10s'; + const fixturePathR = path.normalize(fixturePath).replace(/\\/g, '\\\\'); // for win32 before(function* () { yield mkdirp(homePath); @@ -57,15 +59,19 @@ describe('test/stop.test.js', () => { // yield killer.end(); yield sleep(waitTime); - // make sure is kill not auto exist - assert(!app.stdout.includes('exist by env')); - assert(app.stdout.includes('[master] receive 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+\"\]/i)); + if (process.platform !== 'win32') { + // make sure is kill not auto exist + assert(!app.stdout.includes('exist by env')); + + assert(app.stdout.includes('[master] receive 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 at ${fixturePath}`)); + assert(killer.stdout.match(/got master pid \["\d+\"\]/i)); + } + }); }); }); @@ -86,21 +92,34 @@ describe('test/stop.test.js', () => { }); it('should stop', function* () { - yield coffee.fork(eggBin, [ 'stop', fixturePath ]) - .debug() - .expect('stdout', /\[egg-scripts] stopping egg application/) - .expect('stdout', /got master pid \["\d+\"\]/i) - .expect('code', 0) - .end(); + if (process.platform !== 'win32') { + yield coffee.fork(eggBin, [ 'stop', fixturePath ]) + .debug() + .expect('stdout', new RegExp(`\\[egg-scripts] stopping egg application at ${fixturePath}`)) + .expect('stdout', /got master pid \["\d+\"\]/i) + .expect('code', 0) + .end(); - yield sleep(waitTime); + yield sleep(waitTime); + + // master log + const stdout = yield fs.readFile(path.join(logDir, 'master-stdout.log'), 'utf-8'); + + assert(stdout.includes('[master] receive signal SIGTERM, closing')); + assert(stdout.includes('[master] exit with code:0')); + assert(stdout.includes('[app_worker] exit with code:0')); + + } else { + yield coffee.fork(eggBin, [ 'stop', fixturePath ]) + .debug() + .expect('stdout', new RegExp(`\\[egg-scripts] stopping egg application at ${fixturePathR}`)) + .expect('stdout', /(got master pid \["\d+\"\])|(\[egg-scripts\] stopped)/i) + .expect('code', 0) + .end(); - // master log - const stdout = yield fs.readFile(path.join(logDir, 'master-stdout.log'), 'utf-8'); + yield sleep(waitTime); - assert(stdout.includes('[master] receive signal SIGTERM, closing')); - assert(stdout.includes('[master] exit with code:0')); - assert(stdout.includes('[app_worker] exit with code:0')); + } yield coffee.fork(eggBin, [ 'stop', fixturePath ]) .debug() @@ -108,6 +127,23 @@ describe('test/stop.test.js', () => { .expect('code', 0) .end(); }); + + if (process.platform === 'win32') { + it('should got pid', function* () { + const port = 7001; + const processList = yield helper.findNodeProcessWin(port); + + assert(Array.isArray(processList) && processList.length); + }); + + it('should got empty pid', function* () { + const port = 0; + const processList = yield helper.findNodeProcessWin(port); + + assert(Array.isArray(processList) && !processList.length); + }); + } + }); describe('stop with not exist', () => { @@ -115,7 +151,7 @@ describe('test/stop.test.js', () => { yield utils.cleanup(fixturePath); yield coffee.fork(eggBin, [ 'stop', fixturePath ]) .debug() - .expect('stdout', /\[egg-scripts] stopping egg application/) + .expect('stdout', new RegExp(`\\[egg-scripts] stopping egg application at ${fixturePathR}`)) .expect('stderr', /can't detect any running egg process/) .expect('code', 0) .end(); diff --git a/test/utils.js b/test/utils.js index 3e97e7a..ad0c6e5 100644 --- a/test/utils.js +++ b/test/utils.js @@ -3,8 +3,9 @@ const helper = require('../lib/helper'); const sleep = require('mz-modules/sleep'); -exports.cleanup = function* (baseDir) { - const processList = yield helper.findNodeProcess(x => x.cmd.includes(`"baseDir":"${baseDir}"`)); +exports.cleanup = function* (baseDir, port) { + port = port ? port : 7001; + const processList = yield helper.findNodeProcess(x => x.cmd.includes(`"baseDir":"${baseDir}"`), port); if (processList.length) { console.log(`cleanup: ${processList.length} to kill`);