То, что я пытаюсь достичь
Этот вопрос связан с другим, недавно закрытым с ужасным хаком ™.
Я пытаюсь написать сценарий, который может быть использован в контексте конвейера CI/build.
Сценарий должен запускать сквозные тесты Protractor для нашего Углового одностраничного приложения (SPA).
Сценарий должен выполнять следующие действия (в порядке):
- запустить микросервис.NET Core под названием "Приложение",
- запустить микросервис.NET Core под названием "Интернет",
- запустить SPA
- выполните команду, выполняющую тесты Protractor
- после того, как шаги 4 завершены (успешно или с ошибкой), завершите процессы, созданные на этапах 1-3. Это абсолютно необходимо, иначе сборка никогда не завершится в CI и/или будут процессы z/b/z/
Проблема
Я не начал работать на шаге 4 ("тест e2e"), потому что я действительно хочу убедиться, что шаг 5 ("очистка") работает по назначению.
Как вы могли догадаться (справа), шаг очистки не работает. В частности, процессы "App" и "Web" по какой-то причине не убиваются и продолжают работать.
Кстати, я убедился, что мой скрипт gulp выполняется с повышенными привилегиями (admin).
Проблема - ОБНОВЛЕНИЕ 1
Я только что открыл прямую причину проблемы (я думаю), я не знаю, в чем причина. Есть 5 запущенных процессов вместо 1, как я ожидал. Например, для процесса App
в диспетчере процессов наблюдаются следующие процессы:
{
"id": 14840,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 12600,
"binary": "dotnet.exe",
"title": "Console"
},
{
"id": 12976,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 5492,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 2636,
"binary": "App.exe",
"title": "Console"
}
Аналогично, для Web
службы создаются пять процессов, а не один:
{
"id": 13264,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 1900,
"binary": "dotnet.exe",
"title": "Console"
},
{
"id": 4668,
"binary": "cmd.exe",
"title": "Console"
},
{
"id": 15520,
"binary": "Web.exe",
"title": "Console"
},
{
"id": 7516,
"binary": "cmd.exe",
"title": "Console"
}
Как я это делаю
В принципе, рабочая лошадь здесь - это runCmdAndListen()
которая отталкивает процессы, запуская cmd
предоставленный в качестве аргумента. Когда функция запускает процесс, является средством Node.js exec()
, он затем createdProcesses
массив createdProcesses
для отслеживания.
Шаг Gulp, называемый CLEANUP = "cleanup"
, отвечает за итерацию через createdProcesses
и .kill('SIGTERM')
для каждого из них, который должен убить все процессы, созданные ранее.
gulpfile.js
(скрипт задачи Gulp)
Импорт и константы
const gulp = require('gulp');
const exec = require('child_process').exec;
const path = require('path');
const RUN_APP = 'run-app';
const RUN_WEB = 'run-web';
const RUN_SPA = 'run-spa';
const CLEANUP = 'cleanup';
const appDirectory = path.join('..', 'App');
const webDirectory = path.join('..', 'Web');
const spaDirectory = path.join('.');
const createdProcesses = [];
runCmdAndListen()
/**
* Runs a command and taps on 'stdout' waiting for a 'resolvePhrase' if provided.
* @param {*} name Title of the process to use in console output.
* @param {*} command Command to execute.
* @param {*} cwd Command working directory.
* @param {*} env Command environment parameters.
* @param {*} resolvePhrase Phrase to wait for in 'stdout' and resolve on.
* @param {*} rejectOnError Flag showing whether to reject on a message in 'stderr' or not.
*/
function runCmdAndListen(name, command, cwd, env, resolvePhrase, rejectOnError) {
const options = { cwd };
if (env) options.env = env;
return new Promise((resolve, reject) => {
const newProcess = exec(command, options);
console.info('Adding a running process with id ${newProcess.pid}');
createdProcesses.push({ childProcess: newProcess, isRunning: true });
newProcess.on('exit', () => {
createdProcesses
.find(({ childProcess, _ }) => childProcess.pid === newProcess.pid)
.isRunning = false;
});
newProcess.stdout
.on('data', chunk => {
if (resolvePhrase && chunk.toString().indexOf(resolvePhrase) >= 0) {
console.info('RESOLVED ${name}/${resolvePhrase}');
resolve();
}
});
newProcess.stderr
.on('data', chunk => {
if (rejectOnError) reject(chunk);
});
if (!resolvePhrase) {
console.info('RESOLVED ${name}');
resolve();
}
});
}
Основные задачи Gulp
gulp.task(RUN_APP, () => runCmdAndListen(
'[App]',
'dotnet run --no-build --no-dependencies',
appDirectory,
{ 'ASPNETCORE_ENVIRONMENT': 'Development' },
'Now listening on:',
true)
);
gulp.task(RUN_WEB, () => runCmdAndListen(
'[Web]',
'dotnet run --no-build --no-dependencies',
webDirectory,
{ 'ASPNETCORE_ENVIRONMENT': 'Development' },
'Now listening on:',
true)
);
gulp.task(RUN_SPA, () => runCmdAndListen(
'[SPA]',
'npm run start-prodish-for-e2e',
spaDirectory,
null,
'webpack: Compiled successfully
',
false)
);
gulp.task(CLEANUP, () => {
createdProcesses
.forEach(({ childProcess, isRunning }) => {
console.warn('Killing child process ${childProcess.pid}');
// if (isRunning) {
childProcess.kill('SIGTERM');
// }
});
});
Задача оркестровки
gulp.task(
'e2e',
gulp.series(
gulp.series(
RUN_APP,
RUN_WEB,
),
RUN_SPA,
CLEANUP,
),
() => console.info('All tasks complete'),
);
gulp.task('default', gulp.series('e2e'));