Skip to content

Commit 590323c

Browse files
committed
Parallelize nightly tests and add more community libraries
1 parent eb23857 commit 590323c

File tree

3 files changed

+331
-93
lines changed

3 files changed

+331
-93
lines changed

.github/jit_check.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
register_shutdown_function(function () {
4+
$status = opcache_get_status(false);
5+
var_dump($status);
6+
7+
if ($status["memory_usage"]["free_memory"] < 10*1024*1024) {
8+
fwrite(STDERR, "Not enough free opcache memory!".PHP_EOL);
9+
}
10+
if ($status["interned_strings_usage"]["free_memory"] < 1*1024*1024) {
11+
fwrite(STDERR, "Not enough free interned strings memory!".PHP_EOL);
12+
}
13+
if ($status["jit"]["buffer_free"] < 10*1024*1024) {
14+
fwrite(STDERR, "Not enough free JIT memory!".PHP_EOL);
15+
}
16+
if (!$status["jit"]["on"]) {
17+
fwrite(STDERR, "JIT is not enabled!".PHP_EOL);
18+
}
19+
20+
unset($status);
21+
while (gc_collect_cycles());
22+
});
23+
24+
$argc--;
25+
array_shift($argv);
26+
27+
$_SERVER['argc']--;
28+
array_shift($_SERVER['argv']);
29+
30+
require $argv[0];

.github/nightly.php

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

0 commit comments

Comments
 (0)