-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: impl parallel app for mocha parallel mode (#130)
- use mochaGlobalSetup/mochaGlobalTeardown to setup agent in mocha master process. - bootstrap app without agent to aviod port conflict.
- Loading branch information
Showing
8 changed files
with
413 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
'use strict'; | ||
|
||
const debug = require('debug')('egg-mock'); | ||
const Base = require('sdk-base'); | ||
const path = require('path'); | ||
const detectPort = require('detect-port'); | ||
|
||
const context = require('../context'); | ||
const formatOptions = require('../format_options'); | ||
const { sleep, rimraf } = require('../utils'); | ||
const mockCustomLoader = require('../mock_custom_loader'); | ||
|
||
const { | ||
APP_INIT, | ||
INIT_ONCE_LISTENER, | ||
INIT_ON_LISTENER, | ||
BIND_EVENT, | ||
consoleLogger, | ||
} = require('./util'); | ||
|
||
class MockAgent extends Base { | ||
constructor(options) { | ||
super({ initMethod: '_init' }); | ||
this.options = options; | ||
this.baseDir = options.baseDir; | ||
this.closed = false; | ||
this[APP_INIT] = false; | ||
this[INIT_ON_LISTENER] = new Set(); | ||
this[INIT_ONCE_LISTENER] = new Set(); | ||
// listen once, otherwise will throw exception when emit error without listenr | ||
this.once('error', err => { | ||
consoleLogger.error(err); | ||
}); | ||
} | ||
|
||
async _init() { | ||
if (this.options.beforeInit) { | ||
await this.options.beforeInit(this); | ||
delete this.options.beforeInit; | ||
} | ||
if (this.options.clean !== false) { | ||
const logDir = path.join(this.options.baseDir, 'logs'); | ||
try { | ||
await rimraf(logDir); | ||
} catch (err) { | ||
/* istanbul ignore next */ | ||
console.error(`remove log dir ${logDir} failed: ${err.stack}`); | ||
} | ||
} | ||
|
||
this.options.clusterPort = process.env.CLUSTER_PORT = await detectPort(); | ||
debug('get clusterPort %s', this.options.clusterPort); | ||
const { Agent } = require(this.options.framework); | ||
|
||
const agent = this._instance = new Agent(Object.assign({}, this.options)); | ||
|
||
// egg-mock plugin need to override egg context | ||
Object.assign(agent.context, context); | ||
mockCustomLoader(agent); | ||
|
||
debug('agent instantiate'); | ||
this[APP_INIT] = true; | ||
debug('this[APP_INIT] = true'); | ||
this[BIND_EVENT](); | ||
debug('http server instantiate'); | ||
await agent.ready(); | ||
|
||
const msg = { | ||
action: 'egg-ready', | ||
data: this.options, | ||
}; | ||
agent.messenger._onMessage(msg); | ||
debug('agent ready'); | ||
} | ||
|
||
[BIND_EVENT]() { | ||
for (const args of this[INIT_ON_LISTENER]) { | ||
debug('on(%s), use cache and pass to app', args); | ||
this._instance.on(...args); | ||
this.removeListener(...args); | ||
} | ||
for (const args of this[INIT_ONCE_LISTENER]) { | ||
debug('once(%s), use cache and pass to app', args); | ||
this._instance.on(...args); | ||
this.removeListener(...args); | ||
} | ||
} | ||
|
||
on(...args) { | ||
if (this[APP_INIT]) { | ||
debug('on(%s), pass to app', args); | ||
this._instance.on(...args); | ||
} else { | ||
debug('on(%s), cache it because app has not init', args); | ||
if (this[INIT_ON_LISTENER]) { | ||
this[INIT_ON_LISTENER].add(args); | ||
} | ||
super.on(...args); | ||
} | ||
} | ||
|
||
once(...args) { | ||
if (this[APP_INIT]) { | ||
debug('once(%s), pass to app', args); | ||
this._instance.once(...args); | ||
} else { | ||
debug('once(%s), cache it because app has not init', args); | ||
if (this[INIT_ONCE_LISTENER]) { | ||
this[INIT_ONCE_LISTENER].add(args); | ||
} | ||
super.on(...args); | ||
} | ||
} | ||
|
||
/** | ||
* close app | ||
* @return {Promise} promise | ||
*/ | ||
async close() { | ||
this.closed = true; | ||
const self = this; | ||
if (self._instance) { | ||
await self._instance.close(); | ||
} else { | ||
// when app init throws an exception, must wait for app quit gracefully | ||
await sleep(200); | ||
} | ||
} | ||
} | ||
|
||
module.exports = function(options) { | ||
options = formatOptions(options); | ||
|
||
return new MockAgent(options); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
const Agent = require('./agent'); | ||
const { getEggOptions } = require('../utils'); | ||
|
||
let agent; | ||
exports.mochaGlobalSetup = async () => { | ||
agent = Agent(getEggOptions()); | ||
await agent.ready(); | ||
}; | ||
|
||
exports.mochaGlobalTeardown = async () => { | ||
if (agent) { | ||
await agent.close(); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
'use strict'; | ||
|
||
const debug = require('debug')('egg-mock'); | ||
const Base = require('sdk-base'); | ||
|
||
const context = require('../context'); | ||
const formatOptions = require('../format_options'); | ||
const { sleep } = require('../utils'); | ||
const mockCustomLoader = require('../mock_custom_loader'); | ||
const mockHttpServer = require('../mock_http_server'); | ||
const { | ||
proxyApp, | ||
APP_INIT, | ||
INIT_ONCE_LISTENER, | ||
INIT_ON_LISTENER, | ||
BIND_EVENT, | ||
consoleLogger, | ||
} = require('./util'); | ||
|
||
class MockApplication extends Base { | ||
constructor(options) { | ||
super({ initMethod: '_init' }); | ||
this.options = options; | ||
this.baseDir = options.baseDir; | ||
this.closed = false; | ||
this[APP_INIT] = false; | ||
this[INIT_ON_LISTENER] = new Set(); | ||
this[INIT_ONCE_LISTENER] = new Set(); | ||
// listen once, otherwise will throw exception when emit error without listenr | ||
this.once('error', err => { | ||
consoleLogger.error(err); | ||
}); | ||
} | ||
|
||
async _init() { | ||
if (this.options.beforeInit) { | ||
await this.options.beforeInit(this); | ||
delete this.options.beforeInit; | ||
} | ||
|
||
this.options.clusterPort = process.env.CLUSTER_PORT; | ||
debug('get clusterPort %s', this.options.clusterPort); | ||
const { Application } = require(this.options.framework); | ||
|
||
const app = this._instance = new Application(Object.assign({}, this.options)); | ||
|
||
// egg-mock plugin need to override egg context | ||
Object.assign(app.context, context); | ||
mockCustomLoader(app); | ||
|
||
debug('app instantiate'); | ||
this[APP_INIT] = true; | ||
debug('this[APP_INIT] = true'); | ||
this[BIND_EVENT](); | ||
debug('http server instantiate'); | ||
mockHttpServer(app); | ||
await app.ready(); | ||
|
||
const msg = { | ||
action: 'egg-ready', | ||
data: this.options, | ||
}; | ||
app.messenger._onMessage(msg); | ||
debug('app ready'); | ||
} | ||
|
||
[BIND_EVENT]() { | ||
for (const args of this[INIT_ON_LISTENER]) { | ||
debug('on(%s), use cache and pass to app', args); | ||
this._instance.on(...args); | ||
this.removeListener(...args); | ||
} | ||
for (const args of this[INIT_ONCE_LISTENER]) { | ||
debug('once(%s), use cache and pass to app', args); | ||
this._instance.on(...args); | ||
this.removeListener(...args); | ||
} | ||
} | ||
|
||
on(...args) { | ||
if (this[APP_INIT]) { | ||
debug('on(%s), pass to app', args); | ||
this._instance.on(...args); | ||
} else { | ||
debug('on(%s), cache it because app has not init', args); | ||
if (this[INIT_ON_LISTENER]) { | ||
this[INIT_ON_LISTENER].add(args); | ||
} | ||
super.on(...args); | ||
} | ||
} | ||
|
||
once(...args) { | ||
if (this[APP_INIT]) { | ||
debug('once(%s), pass to app', args); | ||
this._instance.once(...args); | ||
} else { | ||
debug('once(%s), cache it because app has not init', args); | ||
if (this[INIT_ONCE_LISTENER]) { | ||
this[INIT_ONCE_LISTENER].add(args); | ||
} | ||
super.on(...args); | ||
} | ||
} | ||
|
||
/** | ||
* close app | ||
* @return {Promise} promise | ||
*/ | ||
async close() { | ||
this.closed = true; | ||
const self = this; | ||
if (self._instance) { | ||
await self._instance.close(); | ||
} else { | ||
// when app init throws an exception, must wait for app quit gracefully | ||
await sleep(200); | ||
} | ||
} | ||
} | ||
|
||
module.exports = function(options) { | ||
options = formatOptions(options); | ||
|
||
const app = new MockApplication(options); | ||
return proxyApp(app, options); | ||
}; |
Oops, something went wrong.