|
| 1 | +<?php |
| 2 | + |
| 3 | +putenv("ASAN_OPTIONS=exitcode=139"); |
| 4 | +putenv("SYMFONY_DEPRECATIONS_HELPER=max[total]=999"); |
| 5 | +putenv("PHPSECLIB_ALLOW_JIT=1"); |
| 6 | + |
| 7 | +function printMutex(string $result): void { |
| 8 | + flock(STDOUT, LOCK_EX); |
| 9 | + fwrite(STDOUT, $result.PHP_EOL); |
| 10 | + flock(STDOUT, LOCK_UN); |
| 11 | +} |
| 12 | + |
| 13 | +function e(string $cmd, string $extra = ''): string { |
| 14 | + exec("bash -c ".escapeshellarg("$cmd 2>&1"), $result, $code); |
| 15 | + $result = implode("\n", $result); |
| 16 | + if ($code) { |
| 17 | + printMutex("An error occurred while executing $cmd (status $code, extra info $extra): $result"); |
| 18 | + die(1); |
| 19 | + } |
| 20 | + return $result; |
| 21 | +} |
| 22 | + |
| 23 | +$parallel = (int) ($argv[1] ?? 0); |
| 24 | +$parallel = $parallel ?: ((int)`nproc`); |
| 25 | +$parallel = $parallel ?: 8; |
| 26 | + |
| 27 | +$repos = []; |
| 28 | + |
| 29 | +$repos["phpunit"] = [ |
| 30 | + "https://github.com/sebastianbergmann/phpunit.git", |
| 31 | + "main", |
| 32 | + null, |
| 33 | + ["./phpunit"], |
| 34 | + 1 |
| 35 | +]; |
| 36 | + |
| 37 | +$repos["wordpress"] = [ |
| 38 | + "https://github.com/WordPress/wordpress-develop.git", |
| 39 | + "", |
| 40 | + function (): void { |
| 41 | + $f = file_get_contents('wp-tests-config-sample.php'); |
| 42 | + $f = str_replace('youremptytestdbnamehere', 'test', $f); |
| 43 | + $f = str_replace('yourusernamehere', 'root', $f); |
| 44 | + $f = str_replace('yourpasswordhere', 'root', $f); |
| 45 | + file_put_contents('wp-tests-config.php', $f); |
| 46 | + }, |
| 47 | + ["vendor/bin/phpunit"], |
| 48 | + 1 |
| 49 | +]; |
| 50 | + |
| 51 | +foreach (['amp', 'cache', 'dns', 'file', 'http', 'parallel', 'parser', 'pipeline', 'process', 'serialization', 'socket', 'sync', 'websocket-client', 'websocket-server'] as $repo) { |
| 52 | + $repos["amphp-$repo"] = ["https://github.com/amphp/$repo.git", "", null, ["vendor/bin/phpunit"], 1]; |
| 53 | +} |
| 54 | + |
| 55 | +$repos["laravel"] = [ |
| 56 | + "https://github.com/laravel/framework.git", |
| 57 | + "master", |
| 58 | + function (): void { |
| 59 | + $c = file_get_contents("tests/Filesystem/FilesystemTest.php"); |
| 60 | + $c = str_replace("public function testSharedGet()", "#[\\PHPUnit\\Framework\\Attributes\\Group('skip')]\n public function testSharedGet()", $c); |
| 61 | + file_put_contents("tests/Filesystem/FilesystemTest.php", $c); |
| 62 | + }, |
| 63 | + ["vendor/bin/phpunit", "--exclude-group", "skip"], |
| 64 | + 1 |
| 65 | +]; |
| 66 | + |
| 67 | +foreach (['async', 'cache', 'child-process', 'datagram', 'dns', 'event-loop', 'promise', 'promise-stream', 'promise-timer', 'stream'] as $repo) { |
| 68 | + $repos["reactphp-$repo"] = ["https://github.com/reactphp/$repo.git", "", null, ["vendor/bin/phpunit"], 1]; |
| 69 | +} |
| 70 | + |
| 71 | +$repos["revolt"] = ["https://github.com/revoltphp/event-loop.git", "", null, ["vendor/bin/phpunit"], 2]; |
| 72 | + |
| 73 | +$repos["symfony"] = [ |
| 74 | + "https://github.com/symfony/symfony.git", |
| 75 | + "", |
| 76 | + function (): void { |
| 77 | + e("php ./phpunit install"); |
| 78 | + |
| 79 | + // Test causes a heap-buffer-overflow but I cannot reproduce it locally... |
| 80 | + $c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); |
| 81 | + $c = str_replace("public function testSanitizeDeepNestedString()", "/** @group skip */\n public function testSanitizeDeepNestedString()", $c); |
| 82 | + file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c); |
| 83 | + // Buggy FFI test in Symfony, see https://github.com/symfony/symfony/issues/47668 |
| 84 | + $c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); |
| 85 | + $c = str_replace("*/\n public function testCastNonTrailingCharPointer()", "* @group skip\n */\n public function testCastNonTrailingCharPointer()", $c); |
| 86 | + file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c); |
| 87 | + }, |
| 88 | + function (): iterable { |
| 89 | + $it = new RecursiveDirectoryIterator("src/Symfony"); |
| 90 | + /** @var SplFileInfo $file */ |
| 91 | + foreach(new RecursiveIteratorIterator($it) as $file) { |
| 92 | + if ($file->getBasename() == 'phpunit.xml.dist') { |
| 93 | + yield [ |
| 94 | + getcwd()."/phpunit", |
| 95 | + dirname($file->getRealPath()), |
| 96 | + "--exclude-group", |
| 97 | + "tty,benchmark,intl-data,transient", |
| 98 | + "--exclude-group", |
| 99 | + "skip" |
| 100 | + ]; |
| 101 | + } |
| 102 | + } |
| 103 | + }, |
| 104 | + 1 |
| 105 | +]; |
| 106 | + |
| 107 | +$finalStatus = 0; |
| 108 | +$parentPids = []; |
| 109 | + |
| 110 | +$waitOne = function () use (&$finalStatus, &$parentPids): void { |
| 111 | + $res = pcntl_wait($status); |
| 112 | + if ($res === -1) { |
| 113 | + printMutex("An error occurred while waiting with waitpid!"); |
| 114 | + $finalStatus = $finalStatus ?: 1; |
| 115 | + return; |
| 116 | + } |
| 117 | + if (!isset($parentPids[$res])) { |
| 118 | + printMutex("Unknown PID $res returned!"); |
| 119 | + $finalStatus = $finalStatus ?: 1; |
| 120 | + return; |
| 121 | + } |
| 122 | + $desc = $parentPids[$res]; |
| 123 | + unset($parentPids[$res]); |
| 124 | + if (pcntl_wifexited($status)) { |
| 125 | + $status = pcntl_wexitstatus($status); |
| 126 | + printMutex("Child task $desc exited with status $status"); |
| 127 | + if ($status !== 0) { |
| 128 | + $finalStatus = $status; |
| 129 | + } |
| 130 | + } elseif (pcntl_wifstopped($status)) { |
| 131 | + $status = pcntl_wstopsig($status); |
| 132 | + printMutex("Child task $desc stopped by signal $status"); |
| 133 | + $finalStatus = 1; |
| 134 | + } elseif (pcntl_wifsignaled($status)) { |
| 135 | + $status = pcntl_wtermsig($status); |
| 136 | + printMutex("Child task $desc terminated by signal $status"); |
| 137 | + $finalStatus = 1; |
| 138 | + } |
| 139 | +}; |
| 140 | + |
| 141 | +$waitAll = function () use ($waitOne, &$parentPids): void { |
| 142 | + while ($parentPids) { |
| 143 | + $waitOne(); |
| 144 | + } |
| 145 | +}; |
| 146 | + |
| 147 | +printMutex("Cloning repos..."); |
| 148 | + |
| 149 | +foreach ($repos as $dir => [$repo, $branch, $prepare, $command, $repeat]) { |
| 150 | + $pid = pcntl_fork(); |
| 151 | + if ($pid) { |
| 152 | + $parentPids[$pid] = "clone $dir"; |
| 153 | + continue; |
| 154 | + } |
| 155 | + |
| 156 | + chdir(sys_get_temp_dir()); |
| 157 | + if ($branch) { |
| 158 | + $branch = "--branch $branch"; |
| 159 | + } |
| 160 | + e("git clone $repo $branch --depth 1 $dir"); |
| 161 | + |
| 162 | + exit(0); |
| 163 | +} |
| 164 | + |
| 165 | +$waitAll(); |
| 166 | + |
| 167 | +printMutex("Done cloning repos!"); |
| 168 | + |
| 169 | +printMutex("Preparing repos (max $parallel processes)..."); |
| 170 | +foreach ($repos as $dir => [$repo, $branch, $prepare, $command, $repeat]) { |
| 171 | + chdir(sys_get_temp_dir()."/$dir"); |
| 172 | + $rev = e("git rev-parse HEAD", $dir); |
| 173 | + |
| 174 | + $pid = pcntl_fork(); |
| 175 | + if ($pid) { |
| 176 | + $parentPids[$pid] = "prepare $dir ($rev)"; |
| 177 | + if (count($parentPids) >= $parallel) { |
| 178 | + $waitOne(); |
| 179 | + } |
| 180 | + continue; |
| 181 | + } |
| 182 | + |
| 183 | + e("composer i --ignore-platform-reqs", $dir); |
| 184 | + if ($prepare) { |
| 185 | + $prepare(); |
| 186 | + } |
| 187 | + |
| 188 | + exit(0); |
| 189 | +} |
| 190 | +$waitAll(); |
| 191 | + |
| 192 | +printMutex("Done preparing repos!"); |
| 193 | + |
| 194 | +printMutex("Running tests (max $parallel processes)..."); |
| 195 | +foreach ($repos as $dir => [$repo, $branch, $prepare, $command, $repeat]) { |
| 196 | + chdir(sys_get_temp_dir()."/$dir"); |
| 197 | + $rev = e("git rev-parse HEAD", $dir); |
| 198 | + |
| 199 | + if ($command instanceof Closure) { |
| 200 | + $commands = iterator_to_array($command()); |
| 201 | + } else { |
| 202 | + $commands = [$command]; |
| 203 | + } |
| 204 | + |
| 205 | + foreach ($commands as $idx => $cmd) { |
| 206 | + $cmd = array_merge([ |
| 207 | + 'php', |
| 208 | + '--repeat', |
| 209 | + $repeat, |
| 210 | + '-f', |
| 211 | + __DIR__.'/jit_check.php', |
| 212 | + ], $cmd); |
| 213 | + |
| 214 | + $cmdStr = implode(" ", $cmd); |
| 215 | + |
| 216 | + $pid = pcntl_fork(); |
| 217 | + if ($pid) { |
| 218 | + $parentPids[$pid] = "test $dir ($rev): $cmdStr"; |
| 219 | + if (count($parentPids) >= $parallel) { |
| 220 | + $waitOne(); |
| 221 | + } |
| 222 | + continue; |
| 223 | + } |
| 224 | + |
| 225 | + $output = sys_get_temp_dir()."/out_{$dir}_$idx.txt"; |
| 226 | + |
| 227 | + $p = proc_open($cmd, [ |
| 228 | + ["pipe", "r"], |
| 229 | + ["file", $output, "a"], |
| 230 | + ["file", $output, "a"] |
| 231 | + ], $pipes, sys_get_temp_dir()."/$dir"); |
| 232 | + |
| 233 | + if ($p === false) { |
| 234 | + printMutex("Failure starting $cmdStr"); |
| 235 | + exit(1); |
| 236 | + } |
| 237 | + |
| 238 | + $final = 0; |
| 239 | + $status = proc_close($p); |
| 240 | + if ($status !== 0) { |
| 241 | + if ($status > 128) { |
| 242 | + $final = $status; |
| 243 | + } |
| 244 | + printMutex( |
| 245 | + "$dir ($rev): $cmdStr terminated with status $status:".PHP_EOL |
| 246 | + .file_get_contents($output).PHP_EOL |
| 247 | + ); |
| 248 | + } |
| 249 | + |
| 250 | + exit($final); |
| 251 | + } |
| 252 | +} |
| 253 | + |
| 254 | +$waitAll(); |
| 255 | + |
| 256 | +printMutex("All done!"); |
| 257 | + |
| 258 | +die($finalStatus); |
0 commit comments