Skip to content

Commit 087b3a1

Browse files
committed
Run tests in parallel by default
1 parent f12f1b4 commit 087b3a1

9 files changed

Lines changed: 185 additions & 15 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,15 @@ can be determined using `nproc`.
9797
PHP ships with an extensive test suite, the command `make test` is used after
9898
successful compilation of the sources to run this test suite.
9999

100-
It is possible to run tests using multiple cores by setting `-jN` in
101-
`TEST_PHP_ARGS` or `TESTS`:
100+
Tests run in parallel by default, using up to 10 detected logical processors.
101+
Set `-jN` in `TEST_PHP_ARGS` or `TESTS` to override the worker count:
102102

103103
```shell
104104
make TEST_PHP_ARGS=-j4 test
105105
```
106106

107-
Shall run `make test` with a maximum of 4 concurrent jobs: Generally the maximum
108-
number of jobs should not exceed the number of cores available.
107+
This runs `make test` with a maximum of 4 concurrent jobs. Alternatively,
108+
use `-j1` to run tests sequentially.
109109

110110
Use the `TEST_PHP_ARGS` or `TESTS` variable to test only specific directories:
111111

docs/source/miscellaneous/writing-tests.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ When you are testing your test case it's really important to make sure that you
192192
temporary resources (eg files) that you used in the test. There is a special ``--CLEAN--`` section
193193
to help you do this — see `here <#clean>`_.
194194

195+
Tests run in parallel by default. Mutable resources such as files, directories, ports, database
196+
objects, and IPC identifiers must therefore be unique to each test. Read-only fixtures may be
197+
shared. If a resource cannot be isolated, declare the narrowest applicable conflict using
198+
``--CONFLICTS--`` or a ``CONFLICTS`` file.
199+
195200
Another good check is to look at what lines of code in the PHP source your test case covers. This is
196201
easy to do, there are some instructions on the `PHP Wiki
197202
<https://wiki.php.net/doc/articles/writing-tests>`_.

ext/gd/tests/createfromwbmp2.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ gd
88
?>
99
--FILE--
1010
<?php
11-
$filename = __DIR__ . '/_tmp.wbmp';
11+
$filename = __DIR__ . '/_tmp_createfromwbmp2.wbmp';
1212
$fp = fopen($filename,"wb");
1313
if (!$fp) {
1414
exit("Failed to create <$filename>");

ext/gd/tests/createfromwbmp2_extern.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ imagecreatefromwbmp with invalid wbmp
44
gd
55
--FILE--
66
<?php
7-
$filename = __DIR__ . '/_tmp.wbmp';
7+
$filename = __DIR__ . '/_tmp_createfromwbmp2_extern.wbmp';
88
$fp = fopen($filename,"wb");
99
if (!$fp) {
1010
exit("Failed to create <$filename>");
@@ -41,4 +41,4 @@ unlink($filename);
4141
--EXPECTF--
4242
Warning: imagecreatefromwbmp(): %croduct of memory allocation multiplication would exceed INT_MAX, failing operation gracefully%win %s on line %d
4343

44-
Warning: imagecreatefromwbmp(): "%s_tmp.wbmp" is not a valid WBMP file in %s on line %d
44+
Warning: imagecreatefromwbmp(): "%s_tmp_createfromwbmp2_extern.wbmp" is not a valid WBMP file in %s on line %d

ext/zip/tests/oo_addglob_leak.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ if(!defined("GLOB_BRACE")) die ('skip requires GLOB_BRACE');
1212
$dirname = __DIR__ . '/';
1313
include $dirname . 'utils.inc';
1414

15-
$dirname = __DIR__ . '/__tmp_oo_addglob2/';
15+
$dirname = __DIR__ . '/__tmp_oo_addglob_leak/';
1616
$file = $dirname . 'test.zip';
1717

1818
@mkdir($dirname);
@@ -38,7 +38,7 @@ var_dump($zip->addGlob($dirname . 'bar.*', GLOB_BRACE, $options));
3838
<?php
3939
$dirname = __DIR__ . '/';
4040
include $dirname . 'utils.inc';
41-
rmdir_rf(__DIR__ . '/__tmp_oo_addglob2/');
41+
rmdir_rf(__DIR__ . '/__tmp_oo_addglob_leak/');
4242
?>
4343
--EXPECTF--
4444
array(1) {

run-tests.php

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ function show_usage(): void
3434
php run-tests.php [options] [files] [directories]
3535
3636
Options:
37-
-j<workers> Run up to <workers> simultaneous testing processes in parallel for
38-
quicker testing on systems with multiple logical processors.
39-
Note that this is experimental feature.
37+
-j<workers> Run up to <workers> simultaneous testing processes. By default,
38+
the worker count is detected automatically. Use -j1 to run
39+
tests sequentially.
4040
4141
-l <file> Read the testfiles to be executed from <file>. After the test
4242
has finished all failed tests are written to the same <file>.
@@ -351,6 +351,7 @@ function main(): void
351351
$shuffle = false;
352352
$bless = false;
353353
$workers = null;
354+
$workersExplicit = false;
354355
$context_line_count = 3;
355356
$num_repeats = 1;
356357
$show_progress = true;
@@ -412,6 +413,7 @@ function main(): void
412413

413414
switch ($switch) {
414415
case 'j':
416+
$workersExplicit = true;
415417
$workers = substr($argv[$i], 2);
416418
if ($workers == 0 || !preg_match('/^\d+$/', $workers)) {
417419
error("'$workers' is not a valid number of workers, try e.g. -j16 for 16 workers");
@@ -641,6 +643,19 @@ function main(): void
641643
}
642644
}
643645

646+
if (!$workersExplicit && (!$selected_tests || count($test_files) > 1)) {
647+
$workers = get_default_worker_count();
648+
if (
649+
$workers !== null
650+
&& ($valgrind !== null || isset($environment['SKIP_ASAN']))
651+
) {
652+
$workers = min($workers, 2);
653+
}
654+
if ($workers !== null && !can_create_parallel_worker_socket()) {
655+
$workers = null;
656+
}
657+
}
658+
644659
if ($online === null && !isset($environment['SKIP_ONLINE_TESTS'])) {
645660
$online = false;
646661
}
@@ -800,6 +815,53 @@ function main(): void
800815
}
801816
}
802817

818+
function get_default_worker_count(): ?int
819+
{
820+
if (IS_WINDOWS) {
821+
$workerCount = getenv('NUMBER_OF_PROCESSORS');
822+
return is_string($workerCount) ? parse_default_worker_count($workerCount) : null;
823+
}
824+
825+
$commands = [
826+
'nproc 2>/dev/null',
827+
'getconf _NPROCESSORS_ONLN 2>/dev/null',
828+
'getconf NPROCESSORS_ONLN 2>/dev/null',
829+
'sysctl -n hw.logicalcpu 2>/dev/null',
830+
'sysctl -n hw.ncpu 2>/dev/null',
831+
];
832+
foreach ($commands as $command) {
833+
$workerCount = shell_exec($command);
834+
if (
835+
is_string($workerCount)
836+
&& ($workerCount = parse_default_worker_count($workerCount)) !== null
837+
) {
838+
return $workerCount;
839+
}
840+
}
841+
842+
return null;
843+
}
844+
845+
function parse_default_worker_count(string $workerCount): ?int
846+
{
847+
$workerCount = filter_var(trim($workerCount), FILTER_VALIDATE_INT, [
848+
'options' => ['min_range' => 2],
849+
]);
850+
851+
return $workerCount !== false ? min($workerCount, 10) : null;
852+
}
853+
854+
function can_create_parallel_worker_socket(): bool
855+
{
856+
$socket = @stream_socket_server('tcp://127.0.0.1:0');
857+
if ($socket === false) {
858+
return false;
859+
}
860+
861+
fclose($socket);
862+
return true;
863+
}
864+
803865
function verify_config(string $php): void
804866
{
805867
if (empty($php) || !file_exists($php)) {

sapi/cli/tests/010-2.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ if (substr(PHP_OS, 0, 3) == 'WIN') {
1212

1313
$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
1414

15-
$filename_txt = __DIR__."/010.test.txt";
15+
$filename_txt = __DIR__."/010-R.test.txt";
1616
$filename_txt_escaped = escapeshellarg($filename_txt);
1717

1818
$txt = '

sapi/cli/tests/010.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
1414

1515
$filename = __DIR__."/010.test.php";
1616
$filename_escaped = escapeshellarg($filename);
17-
$filename_txt = __DIR__."/010.test.txt";
17+
$filename_txt = __DIR__."/010-F.test.txt";
1818
$filename_txt_escaped = escapeshellarg($filename_txt);
1919

2020
$code = '
@@ -37,7 +37,7 @@ var_dump(shell_exec("cat $filename_txt_escaped | $php -n -F $filename_escaped"))
3737
--CLEAN--
3838
<?php
3939
@unlink(__DIR__."/010.test.php");
40-
@unlink(__DIR__."/010.test.txt");
40+
@unlink(__DIR__."/010-F.test.txt");
4141
?>
4242
--EXPECT--
4343
string(25) "
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
--TEST--
2+
Automatic worker detection is capped for regular and instrumented runs
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY === 'Windows') {
6+
die('skip requires a POSIX shell');
7+
}
8+
?>
9+
--ENV--
10+
TEST_PHP_FORK_SERVER=0
11+
--FILE--
12+
<?php
13+
$root = __DIR__ . '/automatic_worker_limit_' . getmypid();
14+
$bin = $root . '/bin';
15+
$tests = $root . '/tests';
16+
mkdir($bin, recursive: true);
17+
mkdir($tests);
18+
19+
$nproc = $bin . '/nproc';
20+
file_put_contents($nproc, "#!/bin/sh\nprintf '64\\n'\n");
21+
chmod($nproc, 0755);
22+
23+
$testFiles = [];
24+
for ($i = 0; $i < 11; $i++) {
25+
$testFiles[] = $file = $tests . "/$i.phpt";
26+
file_put_contents($file, <<<PHPT
27+
--TEST--
28+
worker cap $i
29+
--FILE--
30+
<?php echo "ok\\n"; ?>
31+
--EXPECT--
32+
ok
33+
PHPT);
34+
}
35+
36+
$environment = [
37+
'PATH' => $bin . PATH_SEPARATOR . getenv('PATH'),
38+
'TEST_PHP_EXECUTABLE' => getenv('TEST_PHP_EXECUTABLE'),
39+
'TEST_PHP_FORK_SERVER' => '0',
40+
];
41+
foreach (['TEMP', 'TMPDIR'] as $name) {
42+
if (($value = getenv($name)) !== false) {
43+
$environment[$name] = $value;
44+
}
45+
}
46+
47+
$runTests = static function (array $arguments) use ($environment, $testFiles): array {
48+
$process = proc_open(
49+
[
50+
getenv('TEST_PHP_EXECUTABLE'),
51+
dirname(__DIR__, 2) . '/run-tests.php',
52+
'-q',
53+
'--no-progress',
54+
...$arguments,
55+
...$testFiles,
56+
],
57+
[
58+
0 => ['pipe', 'r'],
59+
1 => ['pipe', 'w'],
60+
2 => ['redirect', 1],
61+
],
62+
$pipes,
63+
null,
64+
$environment,
65+
);
66+
fclose($pipes[0]);
67+
$output = stream_get_contents($pipes[1]);
68+
fclose($pipes[1]);
69+
70+
return [proc_close($process), $output];
71+
};
72+
73+
[$exitCode, $output] = $runTests([]);
74+
var_dump($exitCode);
75+
var_dump(str_contains($output, 'Spawning 10 workers...'));
76+
var_dump(str_contains($output, 'Spawning 11 workers...'));
77+
78+
[$exitCode, $output] = $runTests(['--asan']);
79+
var_dump($exitCode);
80+
var_dump(str_contains($output, 'Spawning 2 workers...'));
81+
var_dump(str_contains($output, 'Spawning 10 workers...'));
82+
83+
[$exitCode, $output] = $runTests(['--asan', '-j3']);
84+
var_dump($exitCode);
85+
var_dump(str_contains($output, 'Spawning 3 workers...'));
86+
87+
foreach ($testFiles as $file) {
88+
unlink($file);
89+
}
90+
unlink($nproc);
91+
rmdir($tests);
92+
rmdir($bin);
93+
rmdir($root);
94+
?>
95+
--EXPECT--
96+
int(0)
97+
bool(true)
98+
bool(false)
99+
int(0)
100+
bool(true)
101+
bool(false)
102+
int(0)
103+
bool(true)

0 commit comments

Comments
 (0)