Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Behat basic functions
19
 *
20
 * It does not include MOODLE_INTERNAL because is part of the bootstrap.
21
 *
22
 * This script should not be usually included, neither any of its functions
23
 * used, within mooodle code at all. It's for exclusive use of behat and
24
 * moodle setup.php. For places requiring a different/special behavior
25
 * needing to check if are being run as part of behat tests, use:
26
 *     if (defined('BEHAT_SITE_RUNNING')) { ...
27
 *
28
 * @package    core
29
 * @category   test
30
 * @copyright  2012 David Monllaó
31
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32
 */
33
 
34
require_once(__DIR__ . '/../testing/lib.php');
35
 
36
define('BEHAT_EXITCODE_CONFIG', 250);
37
define('BEHAT_EXITCODE_REQUIREMENT', 251);
38
define('BEHAT_EXITCODE_PERMISSIONS', 252);
39
define('BEHAT_EXITCODE_REINSTALL', 253);
40
define('BEHAT_EXITCODE_INSTALL', 254);
41
define('BEHAT_EXITCODE_INSTALLED', 256);
42
 
43
/**
44
 * The behat test site fullname and shortname.
45
 */
46
define('BEHAT_PARALLEL_SITE_NAME', "behatrun");
47
 
48
/**
49
 * Exits with an error code
50
 *
51
 * @param  mixed $errorcode
52
 * @param  string $text
53
 * @return void Stops execution with error code
54
 */
55
function behat_error($errorcode, $text = '') {
56
 
57
    // Adding error prefixes.
58
    switch ($errorcode) {
59
        case BEHAT_EXITCODE_CONFIG:
60
            $text = 'Behat config error: ' . $text;
61
            break;
62
        case BEHAT_EXITCODE_REQUIREMENT:
63
            $text = 'Behat requirement not satisfied: ' . $text;
64
            break;
65
        case BEHAT_EXITCODE_PERMISSIONS:
66
            $text = 'Behat permissions problem: ' . $text . ', check the permissions';
67
            break;
68
        case BEHAT_EXITCODE_REINSTALL:
69
            $path = testing_cli_argument_path('/admin/tool/behat/cli/init.php');
70
            $text = "Reinstall Behat: ".$text.", use:\n php ".$path;
71
            break;
72
        case BEHAT_EXITCODE_INSTALL:
73
            $path = testing_cli_argument_path('/admin/tool/behat/cli/init.php');
74
            $text = "Install Behat before enabling it, use:\n php ".$path;
75
            break;
76
        case BEHAT_EXITCODE_INSTALLED:
77
            $text = "The Behat site is already installed";
78
            break;
79
        default:
80
            $text = 'Unknown error ' . $errorcode . ' ' . $text;
81
            break;
82
    }
83
 
84
    testing_error($errorcode, $text);
85
}
86
 
87
/**
88
 * Return logical error string.
89
 *
90
 * @param int $errtype php error type.
91
 * @return string string which will be returned.
92
 */
93
function behat_get_error_string($errtype) {
94
    switch ($errtype) {
95
        case E_USER_ERROR:
96
            $errnostr = 'Fatal error';
97
            break;
98
        case E_WARNING:
99
        case E_USER_WARNING:
100
            $errnostr = 'Warning';
101
            break;
102
        case E_NOTICE:
103
        case E_USER_NOTICE:
104
        case E_STRICT:
105
            $errnostr = 'Notice';
106
            break;
107
        case E_RECOVERABLE_ERROR:
108
            $errnostr = 'Catchable';
109
            break;
110
        default:
111
            $errnostr = 'Unknown error type';
112
    }
113
 
114
    return $errnostr;
115
}
116
 
117
/**
118
 * PHP errors handler to use when running behat tests.
119
 *
120
 * Adds specific CSS classes to identify
121
 * the messages.
122
 *
123
 * @param int $errno
124
 * @param string $errstr
125
 * @param string $errfile
126
 * @param int $errline
127
 * @return bool
128
 */
129
function behat_error_handler($errno, $errstr, $errfile, $errline) {
130
 
131
    // If is preceded by an @ we don't show it.
132
    if (!error_reporting()) {
133
        return true;
134
    }
135
 
136
    // This error handler receives E_ALL | E_STRICT, running the behat test site the debug level is
137
    // set to DEVELOPER and will always include E_NOTICE,E_USER_NOTICE... as part of E_ALL, if the current
138
    // error_reporting() value does not include one of those levels is because it has been forced through
139
    // the moodle code (see fix_utf8() for example) in that cases we respect the forced error level value.
140
    $respect = array(E_NOTICE, E_USER_NOTICE, E_STRICT, E_WARNING, E_USER_WARNING, E_DEPRECATED, E_USER_DEPRECATED);
141
    foreach ($respect as $respectable) {
142
 
143
        // If the current value does not include this kind of errors and the reported error is
144
        // at that level don't print anything.
145
        if ($errno == $respectable && !(error_reporting() & $respectable)) {
146
            return true;
147
        }
148
    }
149
 
150
    // Using the default one in case there is a fatal catchable error.
151
    default_error_handler($errno, $errstr, $errfile, $errline);
152
 
153
    $errnostr = behat_get_error_string($errno);
154
 
155
    // If ajax script then throw exception, so the calling api catch it and show it on web page.
156
    if (defined('AJAX_SCRIPT')) {
157
        throw new Exception("$errnostr: $errstr in $errfile on line $errline");
158
    } else {
159
        // Wrapping the output.
160
        echo '<div class="phpdebugmessage" data-rel="phpdebugmessage">' . PHP_EOL;
161
        echo "$errnostr: $errstr in $errfile on line $errline" . PHP_EOL;
162
        echo '</div>';
163
    }
164
 
165
    // Also use the internal error handler so we keep the usual behaviour.
166
    return false;
167
}
168
 
169
/**
170
 * Before shutdown save last error entries, so we can fail the test.
171
 */
172
function behat_shutdown_function() {
173
    // If any error found, then save it.
174
    if ($error = error_get_last()) {
175
        // Ignore E_WARNING, as they might come via ( @ )suppression and might lead to false failure.
176
        if (isset($error['type']) && !($error['type'] & E_WARNING)) {
177
 
178
            $errors = behat_get_shutdown_process_errors();
179
 
180
            $errors[] = $error;
181
            $errorstosave = json_encode($errors);
182
 
183
            set_config('process_errors', $errorstosave, 'tool_behat');
184
        }
185
    }
186
}
187
 
188
/**
189
 * Return php errors save which were save during shutdown.
190
 *
191
 * @return array
192
 */
193
function behat_get_shutdown_process_errors() {
194
    global $DB;
195
 
196
    // Don't use get_config, as it use cache and return invalid value, between selenium and cli process.
197
    $phperrors = $DB->get_field('config_plugins', 'value', array('name' => 'process_errors', 'plugin' => 'tool_behat'));
198
 
199
    if (!empty($phperrors)) {
200
        return json_decode($phperrors, true);
201
    } else {
202
        return array();
203
    }
204
}
205
 
206
/**
207
 * Restrict the config.php settings allowed.
208
 *
209
 * When running the behat features the config.php
210
 * settings should not affect the results.
211
 *
212
 * @return void
213
 */
214
function behat_clean_init_config() {
215
    global $CFG;
216
 
217
    $allowed = array_flip(array(
218
        'wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
219
        'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix',
220
        'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword',
221
        'proxybypass', 'pathtogs', 'pathtophp', 'pathtodu', 'aspellpath', 'pathtodot', 'skiplangupgrade',
222
        'altcacheconfigpath', 'pathtounoconv', 'alternative_file_system_class', 'pathtopython'
223
    ));
224
 
225
    // Add extra allowed settings.
226
    if (!empty($CFG->behat_extraallowedsettings)) {
227
        $allowed = array_merge($allowed, array_flip($CFG->behat_extraallowedsettings));
228
    }
229
 
230
    // Also allowing behat_ prefixed attributes.
231
    foreach ($CFG as $key => $value) {
232
        if (!isset($allowed[$key]) && strpos($key, 'behat_') !== 0) {
233
            unset($CFG->{$key});
234
        }
235
    }
236
}
237
 
238
/**
239
 * Checks that the behat config vars are properly set.
240
 *
241
 * @return void Stops execution with error code if something goes wrong.
242
 */
243
function behat_check_config_vars() {
244
    global $CFG;
245
 
246
    $moodleprefix = empty($CFG->prefix) ? '' : $CFG->prefix;
247
    $behatprefix = empty($CFG->behat_prefix) ? '' : $CFG->behat_prefix;
248
    $phpunitprefix = empty($CFG->phpunit_prefix) ? '' : $CFG->phpunit_prefix;
249
    $behatdbname = empty($CFG->behat_dbname) ? $CFG->dbname : $CFG->behat_dbname;
250
    $phpunitdbname = empty($CFG->phpunit_dbname) ? $CFG->dbname : $CFG->phpunit_dbname;
251
    $behatdbhost = empty($CFG->behat_dbhost) ? $CFG->dbhost : $CFG->behat_dbhost;
252
    $phpunitdbhost = empty($CFG->phpunit_dbhost) ? $CFG->dbhost : $CFG->phpunit_dbhost;
253
 
254
    // Verify prefix value.
255
    if (empty($CFG->behat_prefix)) {
256
        behat_error(BEHAT_EXITCODE_CONFIG,
257
            'Define $CFG->behat_prefix in config.php');
258
    }
259
    if ($behatprefix == $moodleprefix && $behatdbname == $CFG->dbname && $behatdbhost == $CFG->dbhost) {
260
        behat_error(BEHAT_EXITCODE_CONFIG,
261
            '$CFG->behat_prefix in config.php must be different from $CFG->prefix' .
262
            ' when $CFG->behat_dbname and $CFG->behat_host are not set or when $CFG->behat_dbname equals $CFG->dbname' .
263
            ' and $CFG->behat_dbhost equals $CFG->dbhost');
264
    }
265
    if ($phpunitprefix !== '' && $behatprefix == $phpunitprefix && $behatdbname == $phpunitdbname &&
266
            $behatdbhost == $phpunitdbhost) {
267
        behat_error(BEHAT_EXITCODE_CONFIG,
268
            '$CFG->behat_prefix in config.php must be different from $CFG->phpunit_prefix' .
269
            ' when $CFG->behat_dbname equals $CFG->phpunit_dbname' .
270
            ' and $CFG->behat_dbhost equals $CFG->phpunit_dbhost');
271
    }
272
 
273
    // Verify behat wwwroot value.
274
    if (empty($CFG->behat_wwwroot)) {
275
        behat_error(BEHAT_EXITCODE_CONFIG,
276
            'Define $CFG->behat_wwwroot in config.php');
277
    }
278
    if (!empty($CFG->wwwroot) and $CFG->behat_wwwroot == $CFG->wwwroot) {
279
        behat_error(BEHAT_EXITCODE_CONFIG,
280
            '$CFG->behat_wwwroot in config.php must be different from $CFG->wwwroot');
281
    }
282
 
283
    // Verify behat dataroot value.
284
    if (empty($CFG->behat_dataroot)) {
285
        behat_error(BEHAT_EXITCODE_CONFIG,
286
            'Define $CFG->behat_dataroot in config.php');
287
    }
288
    clearstatcache();
289
    if (!file_exists($CFG->behat_dataroot_parent)) {
290
        $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777;
291
        umask(0);
292
        if (!mkdir($CFG->behat_dataroot_parent, $permissions, true)) {
293
            behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created');
294
        }
295
    }
296
    $CFG->behat_dataroot_parent = realpath($CFG->behat_dataroot_parent);
297
    if (empty($CFG->behat_dataroot_parent) or !is_dir($CFG->behat_dataroot_parent) or !is_writable($CFG->behat_dataroot_parent)) {
298
        behat_error(BEHAT_EXITCODE_CONFIG,
299
            '$CFG->behat_dataroot in config.php must point to an existing writable directory');
300
    }
301
    if (!empty($CFG->dataroot) and $CFG->behat_dataroot_parent == realpath($CFG->dataroot)) {
302
        behat_error(BEHAT_EXITCODE_CONFIG,
303
            '$CFG->behat_dataroot in config.php must be different from $CFG->dataroot');
304
    }
305
    if (!empty($CFG->phpunit_dataroot) and $CFG->behat_dataroot_parent == realpath($CFG->phpunit_dataroot)) {
306
        behat_error(BEHAT_EXITCODE_CONFIG,
307
            '$CFG->behat_dataroot in config.php must be different from $CFG->phpunit_dataroot');
308
    }
309
 
310
    // This request is coming from admin/tool/behat/cli/util.php which will call util_single.php. So just return from
311
    // here as we don't need to create a dataroot for single run.
312
    if (defined('BEHAT_PARALLEL_UTIL') && BEHAT_PARALLEL_UTIL && empty($CFG->behatrunprocess)) {
313
        return;
314
    }
315
 
316
    if (!file_exists($CFG->behat_dataroot)) {
317
        $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777;
318
        umask(0);
319
        if (!mkdir($CFG->behat_dataroot, $permissions, true)) {
320
            behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created');
321
        }
322
    }
323
    $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
324
}
325
 
326
/**
327
 * Should we switch to the test site data?
328
 * @return bool
329
 */
330
function behat_is_test_site() {
331
    global $CFG;
332
 
333
    if (defined('BEHAT_UTIL')) {
334
        // This is the admin tool that installs/drops the test site install.
335
        return true;
336
    }
337
    if (defined('BEHAT_TEST')) {
338
        // This is the main vendor/bin/behat script.
339
        return true;
340
    }
341
    if (empty($CFG->behat_wwwroot)) {
342
        return false;
343
    }
344
    if (isset($_SERVER['REMOTE_ADDR']) and behat_is_requested_url($CFG->behat_wwwroot)) {
345
        // Something is accessing the web server like a real browser.
346
        return true;
347
    }
348
 
349
    return false;
350
}
351
 
352
/**
353
 * Fix variables for parallel behat testing.
354
 * - behat_wwwroot = behat_wwwroot{behatrunprocess}
355
 * - behat_dataroot = behat_dataroot{behatrunprocess}
356
 * - behat_prefix = behat_prefix.{behatrunprocess}_ (For oracle it will be firstletter of prefix and behatrunprocess)
357
 **/
358
function behat_update_vars_for_process() {
359
    global $CFG;
360
 
361
    $allowedconfigoverride = array('dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'behat_prefix',
362
        'behat_wwwroot', 'behat_dataroot');
363
    $behatrunprocess = behat_get_run_process();
364
    $CFG->behatrunprocess = $behatrunprocess;
365
 
366
    // Data directory will be a directory under parent directory.
367
    $CFG->behat_dataroot_parent = $CFG->behat_dataroot;
368
    $CFG->behat_dataroot .= '/'. BEHAT_PARALLEL_SITE_NAME;
369
 
370
    if ($behatrunprocess) {
371
        if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_wwwroot'])) {
372
            // Set www root for run process.
373
            if (isset($CFG->behat_wwwroot) &&
374
                !preg_match("#/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess . "\$#", $CFG->behat_wwwroot)) {
375
                $CFG->behat_wwwroot .= "/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess;
376
            }
377
        }
378
 
379
        if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_dataroot'])) {
380
            // Set behat_dataroot.
381
            if (!preg_match("#" . $behatrunprocess . "\$#", $CFG->behat_dataroot)) {
382
                $CFG->behat_dataroot .= $behatrunprocess;
383
            }
384
        }
385
 
386
        // Set behat_prefix for db, just suffix run process number, to avoid max length exceed.
387
        // For oracle only 2 letter prefix is possible.
388
        // NOTE: This will not work for parallel process > 9.
389
        if ($CFG->dbtype === 'oci') {
390
            $CFG->behat_prefix = substr($CFG->behat_prefix, 0, 1);
391
            $CFG->behat_prefix .= "{$behatrunprocess}";
392
        } else {
393
            $CFG->behat_prefix .= "{$behatrunprocess}_";
394
        }
395
 
396
        if (!empty($CFG->behat_parallel_run[$behatrunprocess - 1])) {
397
            // Override allowed config vars.
398
            foreach ($allowedconfigoverride as $config) {
399
                if (isset($CFG->behat_parallel_run[$behatrunprocess - 1][$config])) {
400
                    $CFG->$config = $CFG->behat_parallel_run[$behatrunprocess - 1][$config];
401
                }
402
            }
403
        }
404
    }
405
}
406
 
407
/**
408
 * Checks if the URL requested by the user matches the provided argument
409
 *
410
 * @param string $url
411
 * @return bool Returns true if it matches.
412
 */
413
function behat_is_requested_url($url) {
414
 
415
    $parsedurl = parse_url($url . '/');
416
    if (!isset($parsedurl['port'])) {
417
        $parsedurl['port'] = ($parsedurl['scheme'] === 'https') ? 443 : 80;
418
    }
419
    $parsedurl['path'] = rtrim($parsedurl['path'], '/');
420
 
421
    // Removing the port.
422
    $pos = strpos($_SERVER['HTTP_HOST'], ':');
423
    if ($pos !== false) {
424
        $requestedhost = substr($_SERVER['HTTP_HOST'], 0, $pos);
425
    } else {
426
        $requestedhost = $_SERVER['HTTP_HOST'];
427
    }
428
 
429
    // The path should also match.
430
    if (empty($parsedurl['path'])) {
431
        $matchespath = true;
432
    } else if (strpos($_SERVER['SCRIPT_NAME'], $parsedurl['path']) === 0) {
433
        $matchespath = true;
434
    }
435
 
436
    // The host and the port should match
437
    if ($parsedurl['host'] == $requestedhost && $parsedurl['port'] == $_SERVER['SERVER_PORT'] && !empty($matchespath)) {
438
        return true;
439
    }
440
 
441
    return false;
442
}
443
 
444
/**
445
 * Get behat run process from either $_SERVER or command config.
446
 *
447
 * @return bool|int false if single run, else run process number.
448
 */
449
function behat_get_run_process() {
450
    global $argv, $CFG;
451
    $behatrunprocess = false;
452
 
453
    // Get behat run process, if set.
454
    if (defined('BEHAT_CURRENT_RUN') && BEHAT_CURRENT_RUN) {
455
        $behatrunprocess = BEHAT_CURRENT_RUN;
456
    } else if (!empty($_SERVER['REMOTE_ADDR'])) {
457
        // Try get it from config if present.
458
        if (!empty($CFG->behat_parallel_run)) {
459
            foreach ($CFG->behat_parallel_run as $run => $behatconfig) {
460
                if (isset($behatconfig['behat_wwwroot']) && behat_is_requested_url($behatconfig['behat_wwwroot'])) {
461
                    $behatrunprocess = $run + 1; // We start process from 1.
462
                    break;
463
                }
464
            }
465
        }
466
        // Check if parallel site prefix is used.
467
        if (empty($behatrunprocess) && preg_match('#/' . BEHAT_PARALLEL_SITE_NAME . '(.+?)/#', $_SERVER['REQUEST_URI'])) {
468
            $dirrootrealpath = str_replace("\\", "/", realpath($CFG->dirroot));
469
            $serverrealpath = str_replace("\\", "/", realpath($_SERVER['SCRIPT_FILENAME']));
470
            $afterpath = str_replace($dirrootrealpath.'/', '', $serverrealpath);
471
            if (!$behatrunprocess = preg_filter("#.*/" . BEHAT_PARALLEL_SITE_NAME . "(.+?)/$afterpath#", '$1',
472
                $_SERVER['SCRIPT_FILENAME'])) {
473
                throw new Exception("Unable to determine behat process [afterpath=" . $afterpath .
474
                    ", scriptfilename=" . $_SERVER['SCRIPT_FILENAME'] . "]!");
475
            }
476
        }
477
    } else if (defined('BEHAT_TEST') || defined('BEHAT_UTIL')) {
478
        $behatconfig = '';
479
 
480
        if ($match = preg_filter('#--run=(.+)#', '$1', $argv)) {
481
            // Try to guess the run from the existence of the --run arg.
482
            $behatrunprocess = reset($match);
483
 
484
        } else {
485
            // Try to guess the run from the existence of the --config arg. Note there are 2 alternatives below.
486
            if ($k = array_search('--config', $argv)) {
487
                // Alternative 1: --config /path/to/config.yml => (next arg, pick it).
488
                $behatconfig = str_replace("\\", "/", $argv[$k + 1]);
489
 
490
            } else if ($config = preg_filter('#^(?:--config[ =]*)(.+)$#', '$1', $argv)) {
491
                // Alternative 2: --config=/path/to/config.yml => (same arg, just get the path part).
492
                $behatconfig = str_replace("\\", "/", reset($config));
493
            }
494
 
495
            // Try get it from config if present.
496
            if ($behatconfig) {
497
                if (!empty($CFG->behat_parallel_run)) {
498
                    foreach ($CFG->behat_parallel_run as $run => $parallelconfig) {
499
                        if (!empty($parallelconfig['behat_dataroot']) &&
500
                                $parallelconfig['behat_dataroot'] . '/behat/behat.yml' == $behatconfig) {
501
                            $behatrunprocess = $run + 1; // We start process from 1.
502
                            break;
503
                        }
504
                    }
505
                }
506
                // Check if default behat dataroot increment was done.
507
                if (empty($behatrunprocess)) {
508
                    $behatdataroot = str_replace("\\", "/", $CFG->behat_dataroot . '/' . BEHAT_PARALLEL_SITE_NAME);
509
                    $behatrunprocess = preg_filter("#^{$behatdataroot}" . "(.+?)[/|\\\]behat[/|\\\]behat\.yml#", '$1',
510
                        $behatconfig);
511
                }
512
            }
513
        }
514
    }
515
 
516
    return $behatrunprocess;
517
}
518
 
519
/**
520
 * Execute commands in parallel.
521
 *
522
 * @param array $cmds list of commands to be executed.
523
 * @param string $cwd absolute path of working directory.
524
 * @param int $delay time in seconds to add delay between each parallel process.
525
 * @return array list of processes.
526
 */
527
function cli_execute_parallel($cmds, $cwd = null, $delay = 0) {
528
    require_once(__DIR__ . "/../../vendor/autoload.php");
529
 
530
    $processes = array();
531
 
532
    // Create child process.
533
    foreach ($cmds as $name => $cmd) {
534
        if (method_exists('\\Symfony\\Component\\Process\\Process', 'fromShellCommandline')) {
535
            // Process 4.2 and up.
536
            $process = Symfony\Component\Process\Process::fromShellCommandline($cmd);
537
        } else {
538
            // Process 4.1 and older.
539
            $process = new Symfony\Component\Process\Process(null);
540
            $process->setCommandLine($cmd);
541
        }
542
 
543
        $process->setWorkingDirectory($cwd);
544
        $process->setTimeout(null);
545
        $processes[$name] = $process;
546
        $processes[$name]->start();
547
 
548
        // If error creating process then exit.
549
        if ($processes[$name]->getStatus() !== 'started') {
550
            echo "Error starting process: $name";
551
            foreach ($processes[$name] as $process) {
552
                if ($process) {
553
                    $process->signal(SIGKILL);
554
                }
555
            }
556
            exit(1);
557
        }
558
 
559
        // Sleep for specified delay.
560
        if ($delay) {
561
            sleep($delay);
562
        }
563
    }
564
    return $processes;
565
}
566
 
567
/**
568
 * Get command flags for an option/value combination
569
 *
570
 * @param string $option
571
 * @param string|bool|null $value
572
 * @return string
573
 */
574
function behat_get_command_flags(string $option, $value): string {
575
    $commandoptions = '';
576
    if (is_bool($value)) {
577
        if ($value) {
578
            return " --{$option}";
579
        } else {
580
            return " --no-{$option}";
581
        }
582
    } else if ($value !== null) {
583
        return " --$option=\"$value\"";
584
    }
585
    return '';
586
}