Тестовая оркестровка E2E с Gulp в Windows: невозможно убить процесс (ы) - PullRequest
0 голосов
/ 11 июня 2018

Чего я пытаюсь достичь

Этот вопрос связан с другим, который я недавно закрыл с ужасным хаком ™.

Я пытаюсьнаписать скрипт, который можно использовать как шаг в контексте CI / build pipe .

Скрипт должен запускать сквозные тесты на базе транспортира для нашего Angularодностраничное приложение (SPA).

Сценарий должен выполнять следующие действия (по порядку):

  1. запускать микросервис .NET Core под названием «Приложение»
  2. запустить микросервис .NET Core с именем «Web»
  3. запустить SPA
  4. запустить команду, которая выполняет тесты транспортира
  5. после выполнения шагов 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'));

1 Ответ

0 голосов
/ 28 июня 2018
  • dotnet run не передает сигнал уничтожения дочерним элементам в Windows (это поведение в Windows, которое отличается от POSIX OS), вы делаете правильные вещи, то есть управляете дочерними процессами
  • однако SIGTERM не будет работать в Windows: nodejs doc process_signal_events ;это твоя проблема.Возможно, вы захотите попробовать SIGKILL.
  • для чистого решения js, просто process.kill(process.pid, SIGKILL), хотя, возможно, это необходимо проверить: nodejs, проблема 12378 .
  • для надежного решения, проверенного MSFT (но не кроссплатформенного), рассмотрите powershell для управления деревьями благодаря следующим функциям powershell:

    function startproc($mydotnetcommand)
    {
        $parentprocid = Start-Process $mydotnetcommand -passthru
    }
    
    function stopproctree($parentprocid)
    {
        $childpidlist= Get-WmiObject win32_process |`
            where {$_.ParentProcessId -eq $parentprocid}
        Get-Process -Id $childpidlist -ErrorAction SilentlyContinue |`
            Stop-Process -Force
    }
    

    Вы можете использовать 2-ю функцию снаружи psсценарий, передавая родительский PID в качестве аргумента функции stopproctree с помощью:

    param([Int32]$parentprocid)
    stopproctree $parentprocid
    

    (в сценарии, скажем, treecleaner.ps1), а затем powershell.exe -file treecleaner.ps1 -parentprocid XXX

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...