Чего я пытаюсь достичь
Этот вопрос связан с другим, который я недавно закрыл с ужасным хаком ™.
Я пытаюсьнаписать скрипт, который можно использовать как шаг в контексте CI / build pipe .
Скрипт должен запускать сквозные тесты на базе транспортира для нашего Angularодностраничное приложение (SPA).
Сценарий должен выполнять следующие действия (по порядку):
- запускать микросервис .NET Core под названием «Приложение»
- запустить микросервис .NET Core с именем «Web»
- запустить SPA
- запустить команду, которая выполняет тесты транспортира
- после выполнения шагов 4 (либо успешно, либос ошибкой), завершите процессы, созданные на шагах 1-3.Это абсолютно необходимо в противном случае сборка никогда не завершится в CI и / или возникнут процессы Web / App / SPA-зомби, которые нарушат выполнение конвейера в будущем.
Проблема
Я не начал работать над шагом 4 («тест e2e»), потому что я действительно хочу убедиться, что шаг 5 («очистка») работает как задумано.
Как вы могли догадаться (справа), шаг очистки не работает.В частности, процессы "App" и "Web" по какой-то причине не убиваются и продолжают работать.
Кстати, я убедился, что мой скрипт gulp выполняется с повышенными (администраторскими) привилегиями.
Проблема - ОБНОВЛЕНИЕ 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
для отслеживания.
Шаг 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'));