From 433515e8fabf4fc5bc3c9848140096104ea841d3 Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Fri, 25 Oct 2024 15:21:14 +1000 Subject: [PATCH] Issue #3476011 by mondrake: Change run-tests.sh to use Symfony Process instead of proc_open --- core/scripts/run-tests.sh | 63 ++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index 053a09793c6d..a9bd13d1ada0 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -34,6 +34,7 @@ use PHPUnit\Runner\Version; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Process\Process; // cspell:ignore exitcode wwwrun @@ -754,9 +755,12 @@ function simpletest_script_execute_batch(TestRunResultsStorageInterface $test_ru $test_class = array_shift($test_classes); // Fork a child process. $command = simpletest_script_command($test_run, $test_class); - $process = proc_open($command, [], $pipes, NULL, NULL, ['bypass_shell' => TRUE]); - - if (!is_resource($process)) { + try { + $process = new Process($command); + $process->start(); + } + catch (\Exception $e) { + echo get_class($e) . ": " . $e->getMessage() . "\n"; echo "Unable to fork test process. Aborting.\n"; exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS); } @@ -766,7 +770,6 @@ function simpletest_script_execute_batch(TestRunResultsStorageInterface $test_ru 'process' => $process, 'test_run' => $test_run, 'class' => $test_class, - 'pipes' => $pipes, ]; } @@ -775,15 +778,18 @@ function simpletest_script_execute_batch(TestRunResultsStorageInterface $test_ru // Check if some children finished. foreach ($children as $cid => $child) { - $status = proc_get_status($child['process']); - if (empty($status['running'])) { - // The child exited, unregister it. - proc_close($child['process']); - if ($status['exitcode'] === SIMPLETEST_SCRIPT_EXIT_FAILURE) { - $total_status = max($status['exitcode'], $total_status); + if ($child['process']->isTerminated()) { + // The child exited. + echo $child['process']->getOutput(); + $errorOutput = $child['process']->getErrorOutput(); + if ($errorOutput) { + echo 'ERROR: ' . $errorOutput; } - elseif ($status['exitcode']) { - $message = 'FATAL ' . $child['class'] . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').'; + if ($child['process']->getExitCode() === SIMPLETEST_SCRIPT_EXIT_FAILURE) { + $total_status = max($child['process']->getExitCode(), $total_status); + } + elseif ($child['process']->getExitCode()) { + $message = 'FATAL ' . $child['class'] . ': test runner returned a non-zero error code (' . $child['process']->getExitCode() . ').'; echo $message . "\n"; // @todo Return SIMPLETEST_SCRIPT_EXIT_EXCEPTION instead, when // DrupalCI supports this. @@ -866,29 +872,38 @@ function simpletest_script_run_one_test(TestRun $test_run, $test_class) { * @param string $test_class * The name of the test class to run. * - * @return string - * The assembled command string. + * @return list<string> + * The list of command-line elements. */ -function simpletest_script_command(TestRun $test_run, $test_class) { +function simpletest_script_command(TestRun $test_run, string $test_class): array { global $args, $php; - $command = escapeshellarg($php) . ' ' . escapeshellarg('./core/scripts/' . $args['script']); - $command .= ' --url ' . escapeshellarg($args['url']); + $command = []; + $command[] = $php; + $command[] = './core/scripts/' . $args['script']; + $command[] = '--url'; + $command[] = $args['url']; if (!empty($args['sqlite'])) { - $command .= ' --sqlite ' . escapeshellarg($args['sqlite']); + $command[] = '--sqlite'; + $command[] = $args['sqlite']; } if (!empty($args['dburl'])) { - $command .= ' --dburl ' . escapeshellarg($args['dburl']); + $command[] = '--dburl'; + $command[] = $args['dburl']; } - $command .= ' --php ' . escapeshellarg($php); - $command .= " --test-id {$test_run->id()}"; + $command[] = '--php'; + $command[] = $php; + $command[] = '--test-id'; + $command[] = $test_run->id(); foreach (['verbose', 'keep-results', 'color', 'die-on-fail', 'suppress-deprecations'] as $arg) { if ($args[$arg]) { - $command .= ' --' . $arg; + $command[] = '--' . $arg; } } // --execute-test and class name needs to come last. - $command .= ' --execute-test ' . escapeshellarg($test_class); + $command[] = '--execute-test'; + $command[] = $test_class; + return $command; } @@ -1130,7 +1145,7 @@ function get_test_class_method_count(string $class): int { * @return array * An associative array of bins and the test class names in each bin. */ - function place_tests_into_bins(array $tests, int $bin_count) { +function place_tests_into_bins(array $tests, int $bin_count) { // Create a bin corresponding to each parallel test job. $bins = array_fill(0, $bin_count, []); // Go through each test and add them to one bin at a time. -- GitLab