diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index e269ed336408..c2b076059bc0 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -852,7 +852,7 @@ jobs: PHP_BUILD_CACHE_BASE_DIR: C:\build-cache PHP_BUILD_OBJ_DIR: C:\obj PHP_BUILD_CACHE_SDK_DIR: C:\build-cache\sdk - PHP_BUILD_SDK_BRANCH: php-sdk-2.7.1 + PHP_BUILD_SDK_BRANCH: php-sdk-2.8.2 PHP_BUILD_CRT: ${{ fromJson(inputs.branch).jobs.WINDOWS.config.vs_crt_version }} PLATFORM: ${{ matrix.x64 && 'x64' || 'x86' }} THREAD_SAFE: "${{ matrix.zts && '1' || '0' }}" diff --git a/.gitignore b/.gitignore index b76b5a787caa..769ac62cc5d8 100644 --- a/.gitignore +++ b/.gitignore @@ -252,7 +252,7 @@ php # Test results generated by `./run-tests.php` php_test_results_*.txt -# Temporary test information generated by `./run-tests.php` +# Temporary test information generated by `./run-tests.php` (kept for BC; now lives in tmp dir) /run-test-info.php # Temporary POST data placeholder files generated by `./run-tests.php` diff --git a/NEWS b/NEWS index b091ea276142..ae9a19c0cc87 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,9 @@ PHP NEWS operation depth. (iliaal) - Core: + . Changed run-tests.php to run in parallel by default, using up to 10 + automatically detected workers. Pass -j1 for sequential execution. + (NickSdot) . Changed run-tests.php to run test subprocesses without a shell where possible. (NickSdot) . Fixed GH-23083 (SEGV build_trace_args in zend_exceptions.c with @@ -46,6 +49,8 @@ PHP NEWS string. (Weilin Du) . Fixed IntlListFormatter::__construct() leaving stale global error state after successful calls. (Weilin Du) + . Fixed IntlNumberRangeFormatter leaving stale global error state after + successful createFromSkeleton() and format() calls. (Weilin Du) . Implemented GH-20255 (Add a predefined calendar constant in IntlDateFormatter for the proleptic gregorian calendar). (David Carlier) . Added SpoofChecker::areBidiConfusable(). (David Carlier) diff --git a/README.md b/README.md index d83203d74c0b..80618f2d6f94 100644 --- a/README.md +++ b/README.md @@ -97,15 +97,15 @@ can be determined using `nproc`. PHP ships with an extensive test suite, the command `make test` is used after successful compilation of the sources to run this test suite. -It is possible to run tests using multiple cores by setting `-jN` in -`TEST_PHP_ARGS` or `TESTS`: +Tests run in parallel by default, using up to 10 detected logical processors. +Set `-jN` in `TEST_PHP_ARGS` or `TESTS` to override the worker count: ```shell make TEST_PHP_ARGS=-j4 test ``` -Shall run `make test` with a maximum of 4 concurrent jobs: Generally the maximum -number of jobs should not exceed the number of cores available. +This runs `make test` with a maximum of 4 concurrent jobs. Alternatively, +use `-j1` to run tests sequentially. Use the `TEST_PHP_ARGS` or `TESTS` variable to test only specific directories: diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 28cefb1e0afb..3c0d3b4f80fa 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -223,6 +223,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES ======================== - Abstract: + . run-tests.php now runs in parallel by default, using up to 10 automatically + detected workers. Pass -j1 for sequential execution. --asan, --msan, and + Valgrind default to at most two workers. . Minimum required PHP version found on the host system for running scripts like build/gen_stub.php during development has been updated from 7.4 to 8.1. . build/gen_stub.php may now generate a _decl.h file in addition to diff --git a/Zend/tests/gh18572.phpt b/Zend/tests/gh18572.phpt index ff178ebef24f..cf45d2afaaba 100644 --- a/Zend/tests/gh18572.phpt +++ b/Zend/tests/gh18572.phpt @@ -36,4 +36,4 @@ try { } ?> --EXPECTREGEX-- -(Maximum call stack size reached during object comparison|Nesting level too deep - recursive dependency\?) +(Maximum call stack size reached during (object )?comparison|Nesting level too deep - recursive dependency\?) diff --git a/Zend/tests/gh23088.phpt b/Zend/tests/gh23088.phpt new file mode 100644 index 000000000000..59153a1f2ba3 --- /dev/null +++ b/Zend/tests/gh23088.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-23088 (Stack overflow when comparing deeply nested arrays) +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +getMessage(), PHP_EOL; +} + +try { + var_dump($a === $b); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Maximum call stack size reached during comparison +Maximum call stack size reached during comparison diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a2f126fb101d..882b1bf990bf 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5916,6 +5916,8 @@ static void zend_compile_static_var_common(zend_string *var_name, zval *value, u opline = zend_emit_op(NULL, ZEND_BIND_STATIC, NULL, NULL); opline->op1_type = IS_CV; opline->op1.var = lookup_cv(var_name); + + ZEND_STATIC_ASSERT(sizeof(Bucket) % 8 == 0, "Bucket size not compatible with storing flags in lower three bits"); opline->extended_value = (uint32_t)((char*)value - (char*)CG(active_op_array)->static_variables->arData) | mode; } /* }}} */ diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index acc342bc267d..99406f9e4192 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -3221,6 +3221,13 @@ ZEND_API int zend_hash_compare(HashTable *ht1, const HashTable *ht2, compare_fun return 0; } +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_throw_error(NULL, "Maximum call stack size reached during comparison"); + return ZEND_UNCOMPARABLE; + } +#endif + /* It's enough to protect only one of the arrays. * The second one may be referenced from the first and this may cause * false recursion detection. diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst index 8e17674ae481..4339e042e6a4 100644 --- a/docs/source/miscellaneous/writing-tests.rst +++ b/docs/source/miscellaneous/writing-tests.rst @@ -192,6 +192,11 @@ When you are testing your test case it's really important to make sure that you temporary resources (eg files) that you used in the test. There is a special ``--CLEAN--`` section to help you do this — see `here <#clean>`_. +Tests run in parallel by default. Mutable resources such as files, directories, ports, database +objects, and IPC identifiers must therefore be unique to each test. Read-only fixtures may be +shared. If a resource cannot be isolated, declare the narrowest applicable conflict using +``--CONFLICTS--`` or a ``CONFLICTS`` file. + Another good check is to look at what lines of code in the PHP source your test case covers. This is easy to do, there are some instructions on the `PHP Wiki `_. diff --git a/ext/gd/tests/createfromwbmp2.phpt b/ext/gd/tests/createfromwbmp2.phpt index 4608c861323f..7007ff545d74 100644 --- a/ext/gd/tests/createfromwbmp2.phpt +++ b/ext/gd/tests/createfromwbmp2.phpt @@ -8,7 +8,7 @@ gd ?> --FILE-- "); diff --git a/ext/gd/tests/createfromwbmp2_extern.phpt b/ext/gd/tests/createfromwbmp2_extern.phpt index 68895f9a3570..711f2e8ca3ed 100644 --- a/ext/gd/tests/createfromwbmp2_extern.phpt +++ b/ext/gd/tests/createfromwbmp2_extern.phpt @@ -4,7 +4,7 @@ imagecreatefromwbmp with invalid wbmp gd --FILE-- "); @@ -41,4 +41,4 @@ unlink($filename); --EXPECTF-- Warning: imagecreatefromwbmp(): %croduct of memory allocation multiplication would exceed INT_MAX, failing operation gracefully%win %s on line %d -Warning: imagecreatefromwbmp(): "%s_tmp.wbmp" is not a valid WBMP file in %s on line %d +Warning: imagecreatefromwbmp(): "%s_tmp_createfromwbmp2_extern.wbmp" is not a valid WBMP file in %s on line %d diff --git a/ext/intl/rangeformatter/rangeformatter_class.cpp b/ext/intl/rangeformatter/rangeformatter_class.cpp index 2dbb60c5b639..95acfccd2452 100644 --- a/ext/intl/rangeformatter/rangeformatter_class.cpp +++ b/ext/intl/rangeformatter/rangeformatter_class.cpp @@ -88,6 +88,8 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) zend_long collapse; zend_long identityFallback; + intl_error_reset(NULL); + ZEND_PARSE_PARAMETERS_START(4,4) Z_PARAM_STRING(skeleton, skeleton_len) Z_PARAM_STRING(locale, locale_len) @@ -158,7 +160,10 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) zval *start; zval *end; + intl_error_reset(NULL); + IntlNumberRangeFormatter_object* obj = Z_INTL_RANGEFORMATTER_P(ZEND_THIS); + intl_error_reset(RANGEFORMATTER_ERROR_P(obj)); ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_NUMBER(start) @@ -179,13 +184,13 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format) INTL_G(error_level) = 0; if (U_FAILURE(error)) { - intl_error_set(NULL, error, "Failed to format number range"); + intl_errors_set(RANGEFORMATTER_ERROR_P(obj), error, "Failed to format number range"); } zend_string *ret = intl_charFromString(result, &error); if (U_FAILURE(error)) { - intl_error_set(NULL, error, "Failed to convert result to UTF-8"); + intl_errors_set(RANGEFORMATTER_ERROR_P(obj), error, "Failed to convert result to UTF-8"); } INTL_G(use_exceptions) = old_use_exception; diff --git a/ext/intl/tests/rangeformatter/rangeformatter_create_error_reset.phpt b/ext/intl/tests/rangeformatter/rangeformatter_create_error_reset.phpt new file mode 100644 index 000000000000..fb19da87d58d --- /dev/null +++ b/ext/intl/tests/rangeformatter/rangeformatter_create_error_reset.phpt @@ -0,0 +1,54 @@ +--TEST-- +IntlNumberRangeFormatter resets stale errors +--EXTENSIONS-- +intl +--SKIPIF-- + +--FILE-- +format(1, 2); + +var_dump(intl_get_error_code()); +var_dump(intl_get_error_message()); +?> +--EXPECT-- +bool(true) +int(0) +string(12) "U_ZERO_ERROR" +int(0) +string(12) "U_ZERO_ERROR" diff --git a/ext/standard/array.c b/ext/standard/array.c index 584afada2089..4dc12d0f59cc 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1385,6 +1385,13 @@ static zend_result php_array_walk( * levels of recursion. */ zend_fcall_info fci = context->fci; +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return FAILURE; + } +#endif + if (zend_hash_num_elements(target_hash) == 0) { return result; } @@ -4048,6 +4055,13 @@ PHPAPI int php_array_replace_recursive(HashTable *dest, HashTable *src) /* {{{ * zend_ulong num_key; int ret; +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return 0; + } +#endif + ZEND_HASH_FOREACH_KEY_VAL(src, num_key, string_key, src_entry) { src_zval = src_entry; ZVAL_DEREF(src_zval); diff --git a/ext/standard/tests/array/gh23111.phpt b/ext/standard/tests/array/gh23111.phpt new file mode 100644 index 000000000000..21db33a2fc75 --- /dev/null +++ b/ext/standard/tests/array/gh23111.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-23111 (Stack overflow in array_walk_recursive with deeply nested arrays) +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECTF-- +Error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? diff --git a/ext/standard/tests/array/gh23113.phpt b/ext/standard/tests/array/gh23113.phpt new file mode 100644 index 000000000000..894e8d971e53 --- /dev/null +++ b/ext/standard/tests/array/gh23113.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-23113 (Stack overflow in array_replace_recursive with deeply nested arrays) +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- + $a]; +} +try { + array_replace_recursive($a, $a); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +Error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c index 74a559fd591c..6ede828b69fe 100644 --- a/ext/uri/php_uri.c +++ b/ext/uri/php_uri.c @@ -66,13 +66,14 @@ static zend_always_inline zval *php_uri_deref(zval *zv) return zv; } -#define Z_RFC3986_URI_PROP_SCHEME_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 0)) -#define Z_RFC3986_URI_PROP_USERINFO_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 1)) -#define Z_RFC3986_URI_PROP_HOST_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 2)) -#define Z_RFC3986_URI_PROP_PORT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 3)) -#define Z_RFC3986_URI_PROP_PATH_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 4)) -#define Z_RFC3986_URI_PROP_QUERY_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5)) -#define Z_RFC3986_URI_PROP_FRAGMENT_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) +#define Z_RFC3986_URI_PROP_SCHEME_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 0)) +#define Z_RFC3986_URI_PROP_USERINFO_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 1)) +#define Z_RFC3986_URI_PROP_HOST_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 2)) +#define Z_RFC3986_URI_PROP_PORT_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 3)) +#define Z_RFC3986_URI_PROP_PATH_P(zv) OBJ_PROP_NUM(Z_OBJ_P(zv), 4) +#define Z_RFC3986_URI_PROP_PATH_DEREF_P(zv) php_uri_deref(Z_RFC3986_URI_PROP_PATH_P(zv)) +#define Z_RFC3986_URI_PROP_QUERY_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 5)) +#define Z_RFC3986_URI_PROP_FRAGMENT_DEREF_P(zv) php_uri_deref(OBJ_PROP_NUM(Z_OBJ_P(zv), 6)) static HashTable *uri_get_debug_properties(php_uri_object *object) { @@ -1069,14 +1070,17 @@ PHP_METHOD(Uri_Rfc3986_UriBuilder, reset) { ZEND_PARSE_PARAMETERS_NONE(); - convert_to_null(Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS)); - convert_to_null(Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS)); - convert_to_null(Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS)); - convert_to_null(Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS)); - zval_ptr_dtor(Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS)); + zend_object *object = Z_OBJ_P(ZEND_THIS); + zval *property = object->properties_table; + const zval *end = property + object->ce->default_properties_count; + + while (property != end) { + zend_object_dtor_property(object, property); + ZVAL_NULL(property); + property++; + } + ZVAL_EMPTY_STRING(Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS)); - convert_to_null(Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS)); - convert_to_null(Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS)); RETVAL_COPY(ZEND_THIS); } @@ -1219,13 +1223,13 @@ PHP_METHOD(Uri_Rfc3986_UriBuilder, build) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(base_url, php_uri_ce_rfc3986_uri) ZEND_PARSE_PARAMETERS_END(); - const zval *scheme = Z_RFC3986_URI_PROP_SCHEME_P(ZEND_THIS); - const zval *userinfo = Z_RFC3986_URI_PROP_USERINFO_P(ZEND_THIS); - const zval *host = Z_RFC3986_URI_PROP_HOST_P(ZEND_THIS); - const zval *port = Z_RFC3986_URI_PROP_PORT_P(ZEND_THIS); - const zval *path = Z_RFC3986_URI_PROP_PATH_P(ZEND_THIS); - const zval *query = Z_RFC3986_URI_PROP_QUERY_P(ZEND_THIS); - const zval *fragment = Z_RFC3986_URI_PROP_FRAGMENT_P(ZEND_THIS); + const zval *scheme = Z_RFC3986_URI_PROP_SCHEME_DEREF_P(ZEND_THIS); + const zval *userinfo = Z_RFC3986_URI_PROP_USERINFO_DEREF_P(ZEND_THIS); + const zval *host = Z_RFC3986_URI_PROP_HOST_DEREF_P(ZEND_THIS); + const zval *port = Z_RFC3986_URI_PROP_PORT_DEREF_P(ZEND_THIS); + const zval *path = Z_RFC3986_URI_PROP_PATH_DEREF_P(ZEND_THIS); + const zval *query = Z_RFC3986_URI_PROP_QUERY_DEREF_P(ZEND_THIS); + const zval *fragment = Z_RFC3986_URI_PROP_FRAGMENT_DEREF_P(ZEND_THIS); php_uri_parser_rfc3986_uris *base_uris = NULL; if (base_url != NULL) { diff --git a/ext/zip/tests/oo_addglob_leak.phpt b/ext/zip/tests/oo_addglob_leak.phpt index 9040c5565f84..be7f92dccb90 100644 --- a/ext/zip/tests/oo_addglob_leak.phpt +++ b/ext/zip/tests/oo_addglob_leak.phpt @@ -12,7 +12,7 @@ if(!defined("GLOB_BRACE")) die ('skip requires GLOB_BRACE'); $dirname = __DIR__ . '/'; include $dirname . 'utils.inc'; -$dirname = __DIR__ . '/__tmp_oo_addglob2/'; +$dirname = __DIR__ . '/__tmp_oo_addglob_leak/'; $file = $dirname . 'test.zip'; @mkdir($dirname); @@ -38,7 +38,7 @@ var_dump($zip->addGlob($dirname . 'bar.*', GLOB_BRACE, $options)); --EXPECTF-- array(1) { diff --git a/ext/zlib/tests/readgzfile_basic.phpt b/ext/zlib/tests/readgzfile_basic.phpt index ae829406e0d4..cf579879e2fc 100644 --- a/ext/zlib/tests/readgzfile_basic.phpt +++ b/ext/zlib/tests/readgzfile_basic.phpt @@ -10,7 +10,7 @@ is a very common test for all languages EOT; -$dirname = 'readgzfile_temp'; +$dirname = 'readgzfile_basic_temp'; $filename = $dirname.'/readgzfile_basic.txt.gz'; mkdir($dirname); $h = gzopen($filename, 'w'); @@ -19,9 +19,11 @@ gzclose($h); var_dump(readgzfile( $filename ) ); - -unlink($filename); -rmdir($dirname); +?> +--CLEAN-- + --EXPECT-- hello world diff --git a/ext/zlib/tests/readgzfile_basic2.phpt b/ext/zlib/tests/readgzfile_basic2.phpt index ff19bcd35e93..6edb4c85784a 100644 --- a/ext/zlib/tests/readgzfile_basic2.phpt +++ b/ext/zlib/tests/readgzfile_basic2.phpt @@ -10,7 +10,7 @@ is a very common test for all languages EOT; -$dirname = 'readgzfile_temp'; +$dirname = 'readgzfile_basic2_temp'; $filename = $dirname.'/readgzfile_basic2.txt'; mkdir($dirname); $h = fopen($filename, 'w'); @@ -19,9 +19,11 @@ fclose($h); var_dump(readgzfile( $filename ) ); - -unlink($filename); -rmdir($dirname); +?> +--CLEAN-- + --EXPECT-- hello world diff --git a/run-tests.php b/run-tests.php index 6eed6649aec7..ce6bd94db1e0 100755 --- a/run-tests.php +++ b/run-tests.php @@ -34,9 +34,9 @@ function show_usage(): void php run-tests.php [options] [files] [directories] Options: - -j Run up to simultaneous testing processes in parallel for - quicker testing on systems with multiple logical processors. - Note that this is experimental feature. + -j Run up to simultaneous testing processes. By default, + the worker count is detected automatically. Use -j1 to run + tests sequentially. -l Read the testfiles to be executed from . After the test has finished all failed tests are written to the same . @@ -356,6 +356,7 @@ function main(): void $shuffle = false; $bless = false; $workers = null; + $workersExplicit = false; $context_line_count = 3; $num_repeats = 1; $show_progress = true; @@ -417,6 +418,7 @@ function main(): void switch ($switch) { case 'j': + $workersExplicit = true; $workers = substr($argv[$i], 2); if ($workers == 0 || !preg_match('/^\d+$/', $workers)) { error("'$workers' is not a valid number of workers, try e.g. -j16 for 16 workers"); @@ -646,6 +648,17 @@ function main(): void } } + if (!$workersExplicit && (!$selected_tests || count($test_files) > 1)) { + $workers = get_default_worker_count(); + if ($workers !== null + && ($valgrind !== null || isset($environment['SKIP_ASAN']))) { + $workers = min($workers, 2); + } + if ($workers !== null && !can_create_parallel_worker_socket()) { + $workers = null; + } + } + if ($online === null && !isset($environment['SKIP_ONLINE_TESTS'])) { $online = false; } @@ -808,6 +821,53 @@ function main(): void } } +function get_default_worker_count(): ?int +{ + if (IS_WINDOWS) { + $workerCount = getenv('NUMBER_OF_PROCESSORS'); + return is_string($workerCount) ? parse_default_worker_count($workerCount) : null; + } + + $commands = [ + 'nproc 2>/dev/null', + 'getconf _NPROCESSORS_ONLN 2>/dev/null', + 'getconf NPROCESSORS_ONLN 2>/dev/null', + 'sysctl -n hw.logicalcpu 2>/dev/null', + 'sysctl -n hw.ncpu 2>/dev/null', + ]; + foreach ($commands as $command) { + $workerCount = shell_exec($command); + if (is_string($workerCount) + && ($workerCount = parse_default_worker_count($workerCount)) !== null) { + return $workerCount; + } + } + + return null; +} + +function parse_default_worker_count(string $workerCount): ?int +{ + $workerCount = trim($workerCount); + if (preg_match('/^[0-9]+$/D', $workerCount) !== 1) { + return null; + } + + $workerCount = (int) $workerCount; + return $workerCount >= 2 ? min($workerCount, 10) : null; +} + +function can_create_parallel_worker_socket(): bool +{ + $socket = @stream_socket_server('tcp://127.0.0.1:0'); + if ($socket === false) { + return false; + } + + fclose($socket); + return true; +} + function verify_config(string $php): void { if (empty($php) || !file_exists($php)) { @@ -830,7 +890,7 @@ function write_information(array $user_tests, $phpdbg): void $escaped_no_file_cache = escaped_shell_string_from($no_file_cache); // Get info from php - $info_file = __DIR__ . '/run-test-info.php'; + $info_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'run-test-info-' . getmypid() . '.php'; @unlink($info_file); $php_info = ' --EXPECT-- string(25) " diff --git a/tests/run-test/automatic_worker_limit.phpt b/tests/run-test/automatic_worker_limit.phpt new file mode 100644 index 000000000000..5bf6df07365e --- /dev/null +++ b/tests/run-test/automatic_worker_limit.phpt @@ -0,0 +1,107 @@ +--TEST-- +Automatic worker detection is capped for regular and instrumented runs +--SKIPIF-- + +--ENV-- +TEST_PHP_FORK_SERVER=0 +--FILE-- + + --EXPECT-- + ok + PHPT); +} + +$environment = [ + 'PATH' => $bin . PATH_SEPARATOR . getenv('PATH'), + 'TEST_PHP_EXECUTABLE' => getenv('TEST_PHP_EXECUTABLE'), + 'TEST_PHP_FORK_SERVER' => '0', +]; +foreach (['TEMP', 'TMPDIR'] as $name) { + if (($value = getenv($name)) !== false) { + $environment[$name] = $value; + } +} + +$runTests = static function (array $arguments) use ($environment, $testFiles): array { + $process = proc_open( + [ + getenv('TEST_PHP_EXECUTABLE'), + dirname(__DIR__, 2) . '/run-tests.php', + '-q', + '--no-progress', + ...$arguments, + ...$testFiles, + ], + [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['redirect', 1], + ], + $pipes, + null, + $environment, + ); + fclose($pipes[0]); + $output = stream_get_contents($pipes[1]); + fclose($pipes[1]); + + return [proc_close($process), $output]; +}; + +[$exitCode, $output] = $runTests([]); +var_dump($exitCode); +var_dump(str_contains($output, 'Spawning 10 workers...')); +var_dump(str_contains($output, 'Spawning 11 workers...')); + +[$exitCode, $output] = $runTests(['--asan']); +var_dump($exitCode); +var_dump(str_contains($output, 'Spawning 2 workers...')); +var_dump(str_contains($output, 'Spawning 10 workers...')); + +[$exitCode, $output] = $runTests(['--asan', '-j3']); +var_dump($exitCode); +var_dump(str_contains($output, 'Spawning 3 workers...')); +?> +--CLEAN-- + +--EXPECT-- +int(0) +bool(true) +bool(false) +int(0) +bool(true) +bool(false) +int(0) +bool(true)