Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
            $errnostr = 'Notice';
105
            break;
106
        case E_RECOVERABLE_ERROR:
107
            $errnostr = 'Catchable';
108
            break;
109
        default:
110
            $errnostr = 'Unknown error type';
111
    }
112
 
113
    return $errnostr;
114
}
115
 
116
/**
117
 * PHP errors handler to use when running behat tests.
118
 *
119
 * Adds specific CSS classes to identify
120
 * the messages.
121
 *
122
 * @param int $errno
123
 * @param string $errstr
124
 * @param string $errfile
125
 * @param int $errline
126
 * @return bool
127
 */
128
function behat_error_handler($errno, $errstr, $errfile, $errline) {
129
 
130
    // If is preceded by an @ we don't show it.
131
    if (!error_reporting()) {
132
        return true;
133
    }
134
 
1441 ariadna 135
    // This error handler receives E_ALL, running the behat test site the debug level is
1 efrain 136
    // set to DEVELOPER and will always include E_NOTICE,E_USER_NOTICE... as part of E_ALL, if the current
137
    // error_reporting() value does not include one of those levels is because it has been forced through
138
    // the moodle code (see fix_utf8() for example) in that cases we respect the forced error level value.
1441 ariadna 139
    $respect = [E_NOTICE, E_USER_NOTICE, E_WARNING, E_USER_WARNING, E_DEPRECATED, E_USER_DEPRECATED];
1 efrain 140
    foreach ($respect as $respectable) {
141
 
142
        // If the current value does not include this kind of errors and the reported error is
143
        // at that level don't print anything.
144
        if ($errno == $respectable && !(error_reporting() & $respectable)) {
145
            return true;
146
        }
147
    }
148
 
149
    // Using the default one in case there is a fatal catchable error.
150
    default_error_handler($errno, $errstr, $errfile, $errline);
151
 
152
    $errnostr = behat_get_error_string($errno);
153
 
154
    // If ajax script then throw exception, so the calling api catch it and show it on web page.
155
    if (defined('AJAX_SCRIPT')) {
156
        throw new Exception("$errnostr: $errstr in $errfile on line $errline");
157
    } else {
158
        // Wrapping the output.
159
        echo '<div class="phpdebugmessage" data-rel="phpdebugmessage">' . PHP_EOL;
160
        echo "$errnostr: $errstr in $errfile on line $errline" . PHP_EOL;
161
        echo '</div>';
162
    }
163
 
164
    // Also use the internal error handler so we keep the usual behaviour.
165
    return false;
166
}
167
 
168
/**
169
 * Before shutdown save last error entries, so we can fail the test.
170
 */
171
function behat_shutdown_function() {
172
    // If any error found, then save it.
173
    if ($error = error_get_last()) {
174
        // Ignore E_WARNING, as they might come via ( @ )suppression and might lead to false failure.
175
        if (isset($error['type']) && !($error['type'] & E_WARNING)) {
176
 
177
            $errors = behat_get_shutdown_process_errors();
178
 
179
            $errors[] = $error;
180
            $errorstosave = json_encode($errors);
181
 
182
            set_config('process_errors', $errorstosave, 'tool_behat');
183
        }
184
    }
185
}
186
 
187
/**
188
 * Return php errors save which were save during shutdown.
189
 *
190
 * @return array
191
 */
192
function behat_get_shutdown_process_errors() {
193
    global $DB;
194
 
195
    // Don't use get_config, as it use cache and return invalid value, between selenium and cli process.
196
    $phperrors = $DB->get_field('config_plugins', 'value', array('name' => 'process_errors', 'plugin' => 'tool_behat'));
197
 
198
    if (!empty($phperrors)) {
199
        return json_decode($phperrors, true);
200
    } else {
201
        return array();
202
    }
203
}
204
 
205
/**
206
 * Restrict the config.php settings allowed.
207
 *
208
 * When running the behat features the config.php
209
 * settings should not affect the results.
210
 *
211
 * @return void
212
 */
213
function behat_clean_init_config() {
214
    global $CFG;
215
 
216
    $allowed = array_flip(array(
217
        'wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
218
        'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix',
219
        'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword',
220
        'proxybypass', 'pathtogs', 'pathtophp', 'pathtodu', 'aspellpath', 'pathtodot', 'skiplangupgrade',
1441 ariadna 221
        'altcacheconfigpath', 'pathtounoconv', 'alternative_file_system_class', 'pathtopython',
222
        'routerconfigured',
1 efrain 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
    }
1441 ariadna 236
 
237
    // Allow email catcher settings.
238
    if (defined('TEST_EMAILCATCHER_MAIL_SERVER')) {
239
        $CFG->noemailever = false;
240
        $CFG->smtphosts = TEST_EMAILCATCHER_MAIL_SERVER;
241
    }
1 efrain 242
}
243
 
244
/**
245
 * Checks that the behat config vars are properly set.
246
 *
247
 * @return void Stops execution with error code if something goes wrong.
248
 */
249
function behat_check_config_vars() {
250
    global $CFG;
251
 
252
    $moodleprefix = empty($CFG->prefix) ? '' : $CFG->prefix;
253
    $behatprefix = empty($CFG->behat_prefix) ? '' : $CFG->behat_prefix;
254
    $phpunitprefix = empty($CFG->phpunit_prefix) ? '' : $CFG->phpunit_prefix;
255
    $behatdbname = empty($CFG->behat_dbname) ? $CFG->dbname : $CFG->behat_dbname;
256
    $phpunitdbname = empty($CFG->phpunit_dbname) ? $CFG->dbname : $CFG->phpunit_dbname;
257
    $behatdbhost = empty($CFG->behat_dbhost) ? $CFG->dbhost : $CFG->behat_dbhost;
258
    $phpunitdbhost = empty($CFG->phpunit_dbhost) ? $CFG->dbhost : $CFG->phpunit_dbhost;
259
 
260
    // Verify prefix value.
261
    if (empty($CFG->behat_prefix)) {
262
        behat_error(BEHAT_EXITCODE_CONFIG,
263
            'Define $CFG->behat_prefix in config.php');
264
    }
265
    if ($behatprefix == $moodleprefix && $behatdbname == $CFG->dbname && $behatdbhost == $CFG->dbhost) {
266
        behat_error(BEHAT_EXITCODE_CONFIG,
267
            '$CFG->behat_prefix in config.php must be different from $CFG->prefix' .
268
            ' when $CFG->behat_dbname and $CFG->behat_host are not set or when $CFG->behat_dbname equals $CFG->dbname' .
269
            ' and $CFG->behat_dbhost equals $CFG->dbhost');
270
    }
271
    if ($phpunitprefix !== '' && $behatprefix == $phpunitprefix && $behatdbname == $phpunitdbname &&
272
            $behatdbhost == $phpunitdbhost) {
273
        behat_error(BEHAT_EXITCODE_CONFIG,
274
            '$CFG->behat_prefix in config.php must be different from $CFG->phpunit_prefix' .
275
            ' when $CFG->behat_dbname equals $CFG->phpunit_dbname' .
276
            ' and $CFG->behat_dbhost equals $CFG->phpunit_dbhost');
277
    }
278
 
279
    // Verify behat wwwroot value.
280
    if (empty($CFG->behat_wwwroot)) {
281
        behat_error(BEHAT_EXITCODE_CONFIG,
282
            'Define $CFG->behat_wwwroot in config.php');
283
    }
284
    if (!empty($CFG->wwwroot) and $CFG->behat_wwwroot == $CFG->wwwroot) {
285
        behat_error(BEHAT_EXITCODE_CONFIG,
286
            '$CFG->behat_wwwroot in config.php must be different from $CFG->wwwroot');
287
    }
288
 
289
    // Verify behat dataroot value.
290
    if (empty($CFG->behat_dataroot)) {
291
        behat_error(BEHAT_EXITCODE_CONFIG,
292
            'Define $CFG->behat_dataroot in config.php');
293
    }
294
    clearstatcache();
295
    if (!file_exists($CFG->behat_dataroot_parent)) {
296
        $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777;
297
        umask(0);
298
        if (!mkdir($CFG->behat_dataroot_parent, $permissions, true)) {
299
            behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created');
300
        }
301
    }
302
    $CFG->behat_dataroot_parent = realpath($CFG->behat_dataroot_parent);
303
    if (empty($CFG->behat_dataroot_parent) or !is_dir($CFG->behat_dataroot_parent) or !is_writable($CFG->behat_dataroot_parent)) {
304
        behat_error(BEHAT_EXITCODE_CONFIG,
305
            '$CFG->behat_dataroot in config.php must point to an existing writable directory');
306
    }
307
    if (!empty($CFG->dataroot) and $CFG->behat_dataroot_parent == realpath($CFG->dataroot)) {
308
        behat_error(BEHAT_EXITCODE_CONFIG,
309
            '$CFG->behat_dataroot in config.php must be different from $CFG->dataroot');
310
    }
311
    if (!empty($CFG->phpunit_dataroot) and $CFG->behat_dataroot_parent == realpath($CFG->phpunit_dataroot)) {
312
        behat_error(BEHAT_EXITCODE_CONFIG,
313
            '$CFG->behat_dataroot in config.php must be different from $CFG->phpunit_dataroot');
314
    }
315
 
316
    // This request is coming from admin/tool/behat/cli/util.php which will call util_single.php. So just return from
317
    // here as we don't need to create a dataroot for single run.
318
    if (defined('BEHAT_PARALLEL_UTIL') && BEHAT_PARALLEL_UTIL && empty($CFG->behatrunprocess)) {
319
        return;
320
    }
321
 
322
    if (!file_exists($CFG->behat_dataroot)) {
323
        $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777;
324
        umask(0);
325
        if (!mkdir($CFG->behat_dataroot, $permissions, true)) {
326
            behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created');
327
        }
328
    }
329
    $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
330
}
331
 
332
/**
333
 * Should we switch to the test site data?
334
 * @return bool
335
 */
336
function behat_is_test_site() {
337
    global $CFG;
338
 
339
    if (defined('BEHAT_UTIL')) {
340
        // This is the admin tool that installs/drops the test site install.
341
        return true;
342
    }
343
    if (defined('BEHAT_TEST')) {
344
        // This is the main vendor/bin/behat script.
345
        return true;
346
    }
347
    if (empty($CFG->behat_wwwroot)) {
348
        return false;
349
    }
1441 ariadna 350
    if (defined('CLI_SCRIPT') && CLI_SCRIPT && getenv('BEHAT_CLI')) {
351
        // Environment variable makes CLI script run on Behat instance.
352
        echo "BEHAT_CLI: This command line script is running on the acceptance testing site.\n\n";
353
        return true;
354
    }
1 efrain 355
    if (isset($_SERVER['REMOTE_ADDR']) and behat_is_requested_url($CFG->behat_wwwroot)) {
356
        // Something is accessing the web server like a real browser.
357
        return true;
358
    }
359
 
360
    return false;
361
}
362
 
363
/**
364
 * Fix variables for parallel behat testing.
365
 * - behat_wwwroot = behat_wwwroot{behatrunprocess}
366
 * - behat_dataroot = behat_dataroot{behatrunprocess}
1441 ariadna 367
 * - behat_prefix = behat_prefix.{behatrunprocess}
1 efrain 368
 **/
369
function behat_update_vars_for_process() {
370
    global $CFG;
371
 
372
    $allowedconfigoverride = array('dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'behat_prefix',
373
        'behat_wwwroot', 'behat_dataroot');
374
    $behatrunprocess = behat_get_run_process();
375
    $CFG->behatrunprocess = $behatrunprocess;
376
 
377
    // Data directory will be a directory under parent directory.
378
    $CFG->behat_dataroot_parent = $CFG->behat_dataroot;
379
    $CFG->behat_dataroot .= '/'. BEHAT_PARALLEL_SITE_NAME;
380
 
381
    if ($behatrunprocess) {
382
        if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_wwwroot'])) {
383
            // Set www root for run process.
384
            if (isset($CFG->behat_wwwroot) &&
385
                !preg_match("#/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess . "\$#", $CFG->behat_wwwroot)) {
386
                $CFG->behat_wwwroot .= "/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess;
387
            }
388
        }
389
 
390
        if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_dataroot'])) {
391
            // Set behat_dataroot.
392
            if (!preg_match("#" . $behatrunprocess . "\$#", $CFG->behat_dataroot)) {
393
                $CFG->behat_dataroot .= $behatrunprocess;
394
            }
395
        }
396
 
397
        // Set behat_prefix for db, just suffix run process number, to avoid max length exceed.
398
        // NOTE: This will not work for parallel process > 9.
1441 ariadna 399
        $CFG->behat_prefix .= "{$behatrunprocess}_";
1 efrain 400
 
401
        if (!empty($CFG->behat_parallel_run[$behatrunprocess - 1])) {
402
            // Override allowed config vars.
403
            foreach ($allowedconfigoverride as $config) {
404
                if (isset($CFG->behat_parallel_run[$behatrunprocess - 1][$config])) {
405
                    $CFG->$config = $CFG->behat_parallel_run[$behatrunprocess - 1][$config];
406
                }
407
            }
408
        }
409
    }
410
}
411
 
412
/**
413
 * Checks if the URL requested by the user matches the provided argument
414
 *
415
 * @param string $url
416
 * @return bool Returns true if it matches.
417
 */
418
function behat_is_requested_url($url) {
419
 
420
    $parsedurl = parse_url($url . '/');
421
    if (!isset($parsedurl['port'])) {
422
        $parsedurl['port'] = ($parsedurl['scheme'] === 'https') ? 443 : 80;
423
    }
424
    $parsedurl['path'] = rtrim($parsedurl['path'], '/');
425
 
426
    // Removing the port.
427
    $pos = strpos($_SERVER['HTTP_HOST'], ':');
428
    if ($pos !== false) {
429
        $requestedhost = substr($_SERVER['HTTP_HOST'], 0, $pos);
430
    } else {
431
        $requestedhost = $_SERVER['HTTP_HOST'];
432
    }
433
 
434
    // The path should also match.
435
    if (empty($parsedurl['path'])) {
436
        $matchespath = true;
437
    } else if (strpos($_SERVER['SCRIPT_NAME'], $parsedurl['path']) === 0) {
438
        $matchespath = true;
439
    }
440
 
441
    // The host and the port should match
442
    if ($parsedurl['host'] == $requestedhost && $parsedurl['port'] == $_SERVER['SERVER_PORT'] && !empty($matchespath)) {
443
        return true;
444
    }
445
 
446
    return false;
447
}
448
 
449
/**
450
 * Get behat run process from either $_SERVER or command config.
451
 *
452
 * @return bool|int false if single run, else run process number.
453
 */
454
function behat_get_run_process() {
455
    global $argv, $CFG;
456
    $behatrunprocess = false;
457
 
458
    // Get behat run process, if set.
459
    if (defined('BEHAT_CURRENT_RUN') && BEHAT_CURRENT_RUN) {
460
        $behatrunprocess = BEHAT_CURRENT_RUN;
461
    } else if (!empty($_SERVER['REMOTE_ADDR'])) {
462
        // Try get it from config if present.
463
        if (!empty($CFG->behat_parallel_run)) {
464
            foreach ($CFG->behat_parallel_run as $run => $behatconfig) {
465
                if (isset($behatconfig['behat_wwwroot']) && behat_is_requested_url($behatconfig['behat_wwwroot'])) {
466
                    $behatrunprocess = $run + 1; // We start process from 1.
467
                    break;
468
                }
469
            }
470
        }
471
        // Check if parallel site prefix is used.
472
        if (empty($behatrunprocess) && preg_match('#/' . BEHAT_PARALLEL_SITE_NAME . '(.+?)/#', $_SERVER['REQUEST_URI'])) {
473
            $dirrootrealpath = str_replace("\\", "/", realpath($CFG->dirroot));
474
            $serverrealpath = str_replace("\\", "/", realpath($_SERVER['SCRIPT_FILENAME']));
475
            $afterpath = str_replace($dirrootrealpath.'/', '', $serverrealpath);
476
            if (!$behatrunprocess = preg_filter("#.*/" . BEHAT_PARALLEL_SITE_NAME . "(.+?)/$afterpath#", '$1',
477
                $_SERVER['SCRIPT_FILENAME'])) {
478
                throw new Exception("Unable to determine behat process [afterpath=" . $afterpath .
479
                    ", scriptfilename=" . $_SERVER['SCRIPT_FILENAME'] . "]!");
480
            }
481
        }
482
    } else if (defined('BEHAT_TEST') || defined('BEHAT_UTIL')) {
483
        $behatconfig = '';
484
 
485
        if ($match = preg_filter('#--run=(.+)#', '$1', $argv)) {
486
            // Try to guess the run from the existence of the --run arg.
487
            $behatrunprocess = reset($match);
488
 
489
        } else {
490
            // Try to guess the run from the existence of the --config arg. Note there are 2 alternatives below.
491
            if ($k = array_search('--config', $argv)) {
492
                // Alternative 1: --config /path/to/config.yml => (next arg, pick it).
493
                $behatconfig = str_replace("\\", "/", $argv[$k + 1]);
494
 
495
            } else if ($config = preg_filter('#^(?:--config[ =]*)(.+)$#', '$1', $argv)) {
496
                // Alternative 2: --config=/path/to/config.yml => (same arg, just get the path part).
497
                $behatconfig = str_replace("\\", "/", reset($config));
498
            }
499
 
500
            // Try get it from config if present.
501
            if ($behatconfig) {
502
                if (!empty($CFG->behat_parallel_run)) {
503
                    foreach ($CFG->behat_parallel_run as $run => $parallelconfig) {
504
                        if (!empty($parallelconfig['behat_dataroot']) &&
505
                                $parallelconfig['behat_dataroot'] . '/behat/behat.yml' == $behatconfig) {
506
                            $behatrunprocess = $run + 1; // We start process from 1.
507
                            break;
508
                        }
509
                    }
510
                }
511
                // Check if default behat dataroot increment was done.
512
                if (empty($behatrunprocess)) {
513
                    $behatdataroot = str_replace("\\", "/", $CFG->behat_dataroot . '/' . BEHAT_PARALLEL_SITE_NAME);
514
                    $behatrunprocess = preg_filter("#^{$behatdataroot}" . "(.+?)[/|\\\]behat[/|\\\]behat\.yml#", '$1',
515
                        $behatconfig);
516
                }
517
            }
518
        }
519
    }
520
 
521
    return $behatrunprocess;
522
}
523
 
524
/**
525
 * Execute commands in parallel.
526
 *
527
 * @param array $cmds list of commands to be executed.
528
 * @param string $cwd absolute path of working directory.
529
 * @param int $delay time in seconds to add delay between each parallel process.
530
 * @return array list of processes.
531
 */
532
function cli_execute_parallel($cmds, $cwd = null, $delay = 0) {
533
    require_once(__DIR__ . "/../../vendor/autoload.php");
534
 
535
    $processes = array();
536
 
537
    // Create child process.
538
    foreach ($cmds as $name => $cmd) {
539
        if (method_exists('\\Symfony\\Component\\Process\\Process', 'fromShellCommandline')) {
540
            // Process 4.2 and up.
541
            $process = Symfony\Component\Process\Process::fromShellCommandline($cmd);
542
        } else {
543
            // Process 4.1 and older.
544
            $process = new Symfony\Component\Process\Process(null);
545
            $process->setCommandLine($cmd);
546
        }
547
 
548
        $process->setWorkingDirectory($cwd);
549
        $process->setTimeout(null);
550
        $processes[$name] = $process;
551
        $processes[$name]->start();
552
 
553
        // If error creating process then exit.
554
        if ($processes[$name]->getStatus() !== 'started') {
555
            echo "Error starting process: $name";
556
            foreach ($processes[$name] as $process) {
557
                if ($process) {
558
                    $process->signal(SIGKILL);
559
                }
560
            }
561
            exit(1);
562
        }
563
 
564
        // Sleep for specified delay.
565
        if ($delay) {
566
            sleep($delay);
567
        }
568
    }
569
    return $processes;
570
}
571
 
572
/**
573
 * Get command flags for an option/value combination
574
 *
575
 * @param string $option
576
 * @param string|bool|null $value
577
 * @return string
578
 */
579
function behat_get_command_flags(string $option, $value): string {
580
    $commandoptions = '';
581
    if (is_bool($value)) {
582
        if ($value) {
583
            return " --{$option}";
584
        } else {
585
            return " --no-{$option}";
586
        }
587
    } else if ($value !== null) {
588
        return " --$option=\"$value\"";
589
    }
590
    return '';
591
}