同时节点以状态 1 退出。这会停止 Teamcity 导致它相信测试失败

Posted

技术标签:

【中文标题】同时节点以状态 1 退出。这会停止 Teamcity 导致它相信测试失败【英文标题】:Concurrently node exits with status 1. This halts Teamcity leading it to believe that the tests failed 【发布时间】:2018-10-19 20:02:00 【问题描述】:

我正在尝试使用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,即使使用 --success first,我仍然会收到 code 1

[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 优雅地接收到这个信号。似乎没有任何效果。

mock-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);
    );
);

【问题讨论】:

【参考方案1】:

终于找到了解决这个问题的办法。我编写了一个小脚本,将模拟 webapi 和业力测试作为子进程运行。

test-single-run-single-process.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);
);

mock-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 child process - difference between spawn & fork Pass large array to node child process https://azimi.me/2014/12/31/kill-child_process-node-js.html How to kill all child processes on exit? kill all child_process when node process is killed Communicating between two different processes in Node.js

【讨论】:

以上是关于同时节点以状态 1 退出。这会停止 Teamcity 导致它相信测试失败的主要内容,如果未能解决你的问题,请参考以下文章

为啥构建后步骤 (xcopy) 偶尔会在 TeamCity 构建中以代码 2 退出?

所有 sysv 服务都返回退出代码 1(服务 <名称> 启动/状态/停止)

检测进程替换时的退出状态

如何从 Windows Server 2003 故障转移群集中退出节点

如何从父进程获取子进程的状态,即它是停止、继续还是退出?对于 Linux,C 语言

为啥它停止并以退出代码 11 结束?