javascript - 同时节点以状态 1 退出。这会停止 Teamcity 导致它相信测试失败
问题描述
我正在尝试一次运行两个脚本concurrently
。基本命令如下所示:
concurrently -k --success first "node ./tools/mock-webapi/mock-webapi.js" "npm run test-single-run"
依次调用(本地):
"test-single-run": "karma start --single-run --browsers ChromeHeadless"
或在远程(teamcity 主机)上:
"test-teamcity": "karma start --reporters teamcity --single-run --browsers ChromeHeadless",
测试运行良好(本地和远程)。但是,我不断收到退出代码 1。即使我使用concurrently -k --success first
,我仍然会得到一个code 1
与--success first
.
[1] 09 05 2018 17:56:54.032:WARN [launcher]: ChromeHeadless was not killed in 2000 ms, sending SIGKILL.
[1] npm run test-single-run exited with code 0
--> Sending SIGTERM to other processes..
[0] node ./tools/mock-webapi/mock-webapi.js exited with code 1
我尝试了各种方法json-server
来优雅地接收这个信号。似乎没有任何效果。
模拟 webapi.js
process.on('SIGTERM', function (code) {
console.log('Handle SIGTERM', process.pid, code);
exitCode = 0;
server.close(function () {
process.exit(0);
});
});
process.on('SIGKILL', function (code) {
console.log('SIGKILL received...', code);
exitCode = 0;
server.close(function () {
process.exit(0);
});
});
process.on('SIGINT', function (code) {
console.log('SIGINT received...', code);
exitCode = 0;
server.close(function () {
process.exit(0);
});
});
解决方案
终于找到了解决这个问题的办法。我编写了一个小脚本,将模拟 webapi 和业力测试作为子进程运行。
测试单运行单进程.js
const spawn = require('cross-spawn');
/**
* Running the tests requires a mock webapi which is gladly provided by json-server.
* Running the tests in teamcity requires that everything is executed from one terminal.
* Different mock data sets can be easily used if the mockapi is a child process.
* We do not need to keep the mockWebapi alive between trials.
*
* After all the tests have succeeded we then close the json-server by sending (SIGINT).
* Finally the process will exit with code 0, which means for TeamCity that all is OK.
* Now it can proceed with the next build step.
* So basically we can run the tests both standalone and in one single terminal.
* Notice that the mockWebapi is a forked process so we can send messages to it.
*
* <!> Failed approach - Closing the mockwebapi
* Using kill command leaves no option for gracefull shutdown.
* SIGINT or SIGTERM signals are not received by the mockWebapi.
* The server will stay active keeping the 3000 port busy.
*
* mockWebapi.kill('SIGINT');
*/
const fork = require('child_process').fork
const mockWebapi = fork('./tools/mock-webapi/mock-webapi.js')
const karma = spawn(
`karma start --single-run --browsers ChromeHeadless`,
[], { stdio: 'inherit' }
);
// 1) Listen to karma exit
karma.on('close', (code, signal) => {
console.log('Karma closed. Code:', code, 'Signal:', signal);
code === 0 && gracefullyCloseMockWebapi(true);
});
karma.on('error', (code, signal) => {
console.log('Karma error. Code:', code, 'Signal:', signal);
gracefullyCloseMockWebapi(false);
});
let gracefullyCloseMockWebapi = (testsCompletedOk) => {
console.log('Gracefuly close webapi. Tests completed ok:', testsCompletedOk);
mockWebapi.send({ testsCompletedOk });
};
// 2) Finish the job, pass the exit code from mockWeabpi to the command line
mockWebapi.on('close', (code, signal) => {
console.log('Mock webapi closed. Code:', code, 'Signal:', signal);
process.exit(code);
});
模拟 webapi.js
// Some project specific work is done before starting the server
/**
* <!> Failed approach - concurrently
* Dispatching the following command will exit with code 1.
* The SIGTERM or SIGINT handlers are not called after the tests are done.
* concurrently --kill-others --success first
* "node ./tools/mock-webapi/mock-webapi.js" "npm run test-single-run"
*
* process.on('SIGINT', (code) => {
* server.close(function () {
* process.exit(0);
* });
* });
*
* <!> Working approach
* Spawning a process child and sending it a message to self-close.
* Double check it with "echo Exit Code is %errorlevel%" after the processes terminated.
*/
function setupJsonServer(mocks, routes) {
let server = jsonServer.create(),
port = 3000;
const router = jsonServer.router(mocks);
server.use(jsonServer.defaults());
server.use(jsonServer.rewriter(routes));
server.use(router);
server.listen(port, () => {
console.log(`JSON Server is running at ${port}`)
console.log('Process id is', process.pid);
});
process.on('message', function ({ testsCompletedOk }) {
console.log('Mockwebapi will terminate. Tests completed ok', testsCompletedOk)
process.exit(testsCompletedOk === true ? 0 : 1)
});
}
补充阅读:
- https://medium.freecodecamp.org/node-js-child-processes-everything-you-need-to-know-e69498fe970a
- https://github.com/moxystudio/node-cross-spawn
- https://medium.com/@NorbertdeLangen/communicating-between-nodejs-processes-4e68be42b917
- node.js 子进程 - spawn 和 fork 之间的区别
- 将大数组传递给节点子进程
- https://azimi.me/2014/12/31/kill-child_process-node-js.html
- 如何在退出时杀死所有子进程?
- 当节点进程被杀死时杀死所有child_process
- 在 Node.js 中的两个不同进程之间进行通信
推荐阅读
- spring - 为什么 ``PasswordEncoder`` 实现在 Spring Security 5.x 中使用硬编码的 UTF-8 作为输入?
- python-2.7 - 如何通过 Python 编码将所有数据文件从多个文件夹移动到其子文件夹?
- systemd - 无法使用 systemd 将 usbmuxd 作为服务运行,但通过终端执行时可以正常工作
- julia - 在 PlotlyJS 的子图中添加不同的注释/形状
- scala - 为什么 Spark randomSplit API 出现这种奇怪的行为?
- javascript - 如何在我的页面中使用更多 ninja-slider?
- bash - 将 tr 输出返回到变量:找不到命令
- jenkins - 未找到 Jenkins 共享库依赖项
- r - R脚本在每行之后显示状态
- angular - CSRF 过滤器阻止访问(来自 angular,后端:Playframework)