1#!/usr/bin/env php 2<?php 3/* 4 +----------------------------------------------------------------------+ 5 | PHP Version 7 | 6 +----------------------------------------------------------------------+ 7 | Copyright (c) The PHP Group | 8 +----------------------------------------------------------------------+ 9 | This source file is subject to version 3.01 of the PHP license, | 10 | that is bundled with this package in the file LICENSE, and is | 11 | available through the world-wide-web at the following url: | 12 | https://php.net/license/3_01.txt | 13 | If you did not receive a copy of the PHP license and are unable to | 14 | obtain it through the world-wide-web, please send a note to | 15 | license@php.net so we can mail you a copy immediately. | 16 +----------------------------------------------------------------------+ 17 | Authors: Ilia Alshanetsky <iliaa@php.net> | 18 | Preston L. Bannister <pbannister@php.net> | 19 | Marcus Boerger <helly@php.net> | 20 | Derick Rethans <derick@php.net> | 21 | Sander Roobol <sander@php.net> | 22 | Andrea Faulds <ajf@ajf.me> | 23 | (based on version by: Stig Bakken <ssb@php.net>) | 24 | (based on the PHP 3 test framework by Rasmus Lerdorf) | 25 +----------------------------------------------------------------------+ 26 */ 27 28/* $Id: 19b7f5cb4015d00823d154fd0b256389c2d8d6cc $ */ 29 30/* Let there be no top-level code beyond this point: 31 * Only functions and classes, thanks! 32 * 33 * Minimum required PHP version: 7.0.0 34 */ 35 36/** 37 * One function to rule them all, one function to find them, one function to 38 * bring them all and in the darkness bind them. 39 * This is the entry point and exit point überfunction. It contains all the 40 * code that was previously found at the top level. It could and should be 41 * refactored to be smaller and more manageable. 42 */ 43function main() 44{ 45 /* This list was derived in a naïve mechanical fashion. If a member 46 * looks like it doesn't belong, it probably doesn't; cull at will. 47 */ 48 global $DETAILED, $PHP_FAILED_TESTS, $SHOW_ONLY_GROUPS, $argc, $argv, $cfg, 49 $cfgfiles, $cfgtypes, $conf_passed, $end_time, $environment, 50 $exts_skipped, $exts_tested, $exts_to_test, $failed_tests_file, 51 $html_file, $html_output, $ignored_by_ext, $ini_overwrites, $is_switch, 52 $just_save_results, $log_format, $matches, $no_clean, $no_file_cache, 53 $optionals, $output_file, $pass_option_n, $pass_options, 54 $pattern_match, $php, $php_cgi, $phpdbg, $preload, $redir_tests, 55 $repeat, $result_tests_file, $slow_min_ms, $start_time, $switch, 56 $temp_source, $temp_target, $temp_urlbase, $test_cnt, $test_dirs, 57 $test_files, $test_idx, $test_list, $test_results, $testfile, 58 $user_tests, $valgrind, $sum_results, $shuffle; 59 // Parallel testing 60 global $workers, $workerID; 61 62 $workerID = 0; 63 if (getenv("TEST_PHP_WORKER")) { 64 $workerID = intval(getenv("TEST_PHP_WORKER")); 65 run_worker(); 66 return; 67 } 68 69 define('INIT_DIR', getcwd()); 70 71 // change into the PHP source directory. 72 if (getenv('TEST_PHP_SRCDIR')) { 73 @chdir(getenv('TEST_PHP_SRCDIR')); 74 } 75 define('TEST_PHP_SRCDIR', getcwd()); 76 77 if (!function_exists('proc_open')) { 78 echo <<<NO_PROC_OPEN_ERROR 79 80+-----------------------------------------------------------+ 81| ! ERROR ! | 82| The test-suite requires that proc_open() is available. | 83| Please check if you disabled it in php.ini. | 84+-----------------------------------------------------------+ 85 86NO_PROC_OPEN_ERROR; 87 exit(1); 88 } 89 90 // If timezone is not set, use UTC. 91 if (ini_get('date.timezone') == '') { 92 date_default_timezone_set('UTC'); 93 } 94 95 // Delete some security related environment variables 96 putenv('SSH_CLIENT=deleted'); 97 putenv('SSH_AUTH_SOCK=deleted'); 98 putenv('SSH_TTY=deleted'); 99 putenv('SSH_CONNECTION=deleted'); 100 101 set_time_limit(0); 102 103 ini_set('pcre.backtrack_limit', PHP_INT_MAX); 104 105 // delete as much output buffers as possible 106 while (@ob_end_clean()) { 107 ; 108 } 109 if (ob_get_level()) { 110 echo "Not all buffers were deleted.\n"; 111 } 112 113 error_reporting(E_ALL); 114 115 $environment = $_ENV ?? array(); 116 // Note: php.ini-development sets variables_order="GPCS" not "EGPCS", in which case $_ENV is NOT populated. 117 // detect and handle this case, or die or warn 118 if (empty($environment)) { 119 // not documented, but returns array of all environment variables 120 $environment = getenv(); 121 } 122 if (empty($environment['TEMP'])) { 123 $environment['TEMP'] = sys_get_temp_dir(); 124 125 if (empty($environment['TEMP'])) { 126 // for example, OpCache on Windows will fail in this case because child processes (for tests) will not get 127 // a TEMP variable, so GetTempPath() will fallback to c:\windows, while GetTempPath() will return %TEMP% for parent 128 // (likely a different path). The parent will initialize the OpCache in that path, and child will fail to reattach to 129 // the OpCache because it will be using the wrong path. 130 die("TEMP environment is NOT set"); 131 } else { 132 if (count($environment) == 1) { 133 // not having other environment variables, only having TEMP, is probably ok, but strange and may make a 134 // difference in the test pass rate, so warn the user. 135 echo "WARNING: Only 1 environment variable will be available to tests(TEMP environment variable)" . PHP_EOL; 136 } 137 } 138 } 139 // 140 if ((substr(PHP_OS, 0, 3) == "WIN") && empty($environment["SystemRoot"])) { 141 $environment["SystemRoot"] = getenv("SystemRoot"); 142 } 143 144 $php = null; 145 $php_cgi = null; 146 $phpdbg = null; 147 148 if (getenv('TEST_PHP_EXECUTABLE')) { 149 $php = getenv('TEST_PHP_EXECUTABLE'); 150 151 if ($php == 'auto') { 152 $php = TEST_PHP_SRCDIR . '/sapi/cli/php'; 153 putenv("TEST_PHP_EXECUTABLE=$php"); 154 155 if (!getenv('TEST_PHP_CGI_EXECUTABLE')) { 156 $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; 157 158 if (file_exists($php_cgi)) { 159 putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); 160 } else { 161 $php_cgi = null; 162 } 163 } 164 } 165 $environment['TEST_PHP_EXECUTABLE'] = $php; 166 } 167 168 if (getenv('TEST_PHP_CGI_EXECUTABLE')) { 169 $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE'); 170 171 if ($php_cgi == 'auto') { 172 $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; 173 putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); 174 } 175 176 $environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi; 177 } 178 179 if (!getenv('TEST_PHPDBG_EXECUTABLE')) { 180 if (!strncasecmp(PHP_OS, "win", 3) && file_exists(dirname($php) . "/phpdbg.exe")) { 181 $phpdbg = realpath(dirname($php) . "/phpdbg.exe"); 182 } elseif (file_exists(dirname($php) . "/../../sapi/phpdbg/phpdbg")) { 183 $phpdbg = realpath(dirname($php) . "/../../sapi/phpdbg/phpdbg"); 184 } elseif (file_exists("./sapi/phpdbg/phpdbg")) { 185 $phpdbg = realpath("./sapi/phpdbg/phpdbg"); 186 } elseif (file_exists(dirname($php) . "/phpdbg")) { 187 $phpdbg = realpath(dirname($php) . "/phpdbg"); 188 } else { 189 $phpdbg = null; 190 } 191 if ($phpdbg) { 192 putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); 193 } 194 } 195 196 if (getenv('TEST_PHPDBG_EXECUTABLE')) { 197 $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE'); 198 199 if ($phpdbg == 'auto') { 200 $phpdbg = TEST_PHP_SRCDIR . '/sapi/phpdbg/phpdbg'; 201 putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); 202 } 203 204 $environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg; 205 } 206 207 if (getenv('TEST_PHP_LOG_FORMAT')) { 208 $log_format = strtoupper(getenv('TEST_PHP_LOG_FORMAT')); 209 } else { 210 $log_format = 'LEODS'; 211 } 212 213 // Check whether a detailed log is wanted. 214 if (getenv('TEST_PHP_DETAILED')) { 215 $DETAILED = getenv('TEST_PHP_DETAILED'); 216 } else { 217 $DETAILED = 0; 218 } 219 220 junit_init(); 221 222 if (getenv('SHOW_ONLY_GROUPS')) { 223 $SHOW_ONLY_GROUPS = explode(",", getenv('SHOW_ONLY_GROUPS')); 224 } else { 225 $SHOW_ONLY_GROUPS = array(); 226 } 227 228 // Check whether user test dirs are requested. 229 if (getenv('TEST_PHP_USER')) { 230 $user_tests = explode(',', getenv('TEST_PHP_USER')); 231 } else { 232 $user_tests = array(); 233 } 234 235 $exts_to_test = array(); 236 $ini_overwrites = array( 237 'output_handler=', 238 'open_basedir=', 239 'disable_functions=', 240 'output_buffering=Off', 241 'error_reporting=' . E_ALL, 242 'display_errors=1', 243 'display_startup_errors=1', 244 'log_errors=0', 245 'html_errors=0', 246 'track_errors=0', 247 'report_memleaks=1', 248 'report_zend_debug=0', 249 'docref_root=', 250 'docref_ext=.html', 251 'error_prepend_string=', 252 'error_append_string=', 253 'auto_prepend_file=', 254 'auto_append_file=', 255 'ignore_repeated_errors=0', 256 'precision=14', 257 'memory_limit=128M', 258 'log_errors_max_len=0', 259 'opcache.fast_shutdown=0', 260 'opcache.file_update_protection=0', 261 'opcache.revalidate_freq=0', 262 'zend.assertions=1', 263 'zend.exception_ignore_args=0', 264 ); 265 266 $no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0'; 267 268 define('PHP_QA_EMAIL', 'qa-reports@lists.php.net'); 269 define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php'); 270 define('QA_REPORTS_PAGE', 'http://qa.php.net/reports'); 271 define('TRAVIS_CI', (bool)getenv('TRAVIS')); 272 273 // Determine the tests to be run. 274 275 $test_files = array(); 276 $redir_tests = array(); 277 $test_results = array(); 278 $PHP_FAILED_TESTS = array( 279 'BORKED' => array(), 280 'FAILED' => array(), 281 'WARNED' => array(), 282 'LEAKED' => array(), 283 'XFAILED' => array(), 284 'XLEAKED' => array(), 285 'SLOW' => array() 286 ); 287 288 // If parameters given assume they represent selected tests to run. 289 $result_tests_file = false; 290 $failed_tests_file = false; 291 $pass_option_n = false; 292 $pass_options = ''; 293 294 $output_file = INIT_DIR . '/php_test_results_' . date('Ymd_Hi') . '.txt'; 295 296 $just_save_results = false; 297 $valgrind = null; 298 $html_output = false; 299 $html_file = null; 300 $temp_source = null; 301 $temp_target = null; 302 $temp_urlbase = null; 303 $conf_passed = null; 304 $no_clean = false; 305 $slow_min_ms = INF; 306 $preload = false; 307 $shuffle = false; 308 $workers = null; 309 310 $cfgtypes = array('show', 'keep'); 311 $cfgfiles = array('skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem'); 312 $cfg = array(); 313 314 foreach ($cfgtypes as $type) { 315 $cfg[$type] = array(); 316 317 foreach ($cfgfiles as $file) { 318 $cfg[$type][$file] = false; 319 } 320 } 321 322 if (!isset($argc, $argv) || !$argc) { 323 $argv = array(__FILE__); 324 $argc = 1; 325 } 326 327 if (getenv('TEST_PHP_ARGS')) { 328 $argv = array_merge($argv, explode(' ', getenv('TEST_PHP_ARGS'))); 329 $argc = count($argv); 330 } 331 332 for ($i = 1; $i < $argc; $i++) { 333 $is_switch = false; 334 $switch = substr($argv[$i], 1, 1); 335 $repeat = substr($argv[$i], 0, 1) == '-'; 336 337 while ($repeat) { 338 339 if (!$is_switch) { 340 $switch = substr($argv[$i], 1, 1); 341 } 342 343 $is_switch = true; 344 345 if ($repeat) { 346 foreach ($cfgtypes as $type) { 347 if (strpos($switch, '--' . $type) === 0) { 348 foreach ($cfgfiles as $file) { 349 if ($switch == '--' . $type . '-' . $file) { 350 $cfg[$type][$file] = true; 351 $is_switch = false; 352 break; 353 } 354 } 355 } 356 } 357 } 358 359 if (!$is_switch) { 360 $is_switch = true; 361 break; 362 } 363 364 $repeat = false; 365 366 switch ($switch) { 367 case 'j': 368 $workers = substr($argv[$i], 2); 369 if (!preg_match('/^\d+$/', $workers) || $workers == 0) { 370 error("'$workers' is not a valid number of workers, try e.g. -j16 for 16 workers"); 371 } 372 $workers = intval($workers, 10); 373 // Don't use parallel testing infrastructure if there is only one worker. 374 if ($workers === 1) { 375 $workers = null; 376 } 377 break; 378 case 'r': 379 case 'l': 380 $test_list = file($argv[++$i]); 381 if ($test_list) { 382 foreach ($test_list as $test) { 383 $matches = array(); 384 if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) { 385 $redir_tests[] = array($matches[1], $matches[2]); 386 } else { 387 if (strlen($test)) { 388 $test_files[] = trim($test); 389 } 390 } 391 } 392 } 393 if ($switch != 'l') { 394 break; 395 } 396 $i--; 397 // break left intentionally 398 case 'w': 399 $failed_tests_file = fopen($argv[++$i], 'w+t'); 400 break; 401 case 'a': 402 $failed_tests_file = fopen($argv[++$i], 'a+t'); 403 break; 404 case 'W': 405 $result_tests_file = fopen($argv[++$i], 'w+t'); 406 break; 407 case 'c': 408 $conf_passed = $argv[++$i]; 409 break; 410 case 'd': 411 $ini_overwrites[] = $argv[++$i]; 412 break; 413 case 'g': 414 $SHOW_ONLY_GROUPS = explode(",", $argv[++$i]); 415 break; 416 //case 'h' 417 case '--keep-all': 418 foreach ($cfgfiles as $file) { 419 $cfg['keep'][$file] = true; 420 } 421 break; 422 //case 'l' 423 case 'm': 424 $valgrind = new RuntestsValgrind($environment); 425 break; 426 case 'M': 427 $valgrind = new RuntestsValgrind($environment, $argv[++$i]); 428 break; 429 case 'n': 430 if (!$pass_option_n) { 431 $pass_options .= ' -n'; 432 } 433 $pass_option_n = true; 434 break; 435 case 'e': 436 $pass_options .= ' -e'; 437 break; 438 case '--preload': 439 $preload = true; 440 break; 441 case '--no-clean': 442 $no_clean = true; 443 break; 444 case 'p': 445 $php = $argv[++$i]; 446 putenv("TEST_PHP_EXECUTABLE=$php"); 447 $environment['TEST_PHP_EXECUTABLE'] = $php; 448 break; 449 case 'P': 450 $php = PHP_BINARY; 451 putenv("TEST_PHP_EXECUTABLE=$php"); 452 $environment['TEST_PHP_EXECUTABLE'] = $php; 453 break; 454 case 'q': 455 putenv('NO_INTERACTION=1'); 456 $environment['NO_INTERACTION'] = 1; 457 break; 458 //case 'r' 459 case 's': 460 $output_file = $argv[++$i]; 461 $just_save_results = true; 462 break; 463 case '--set-timeout': 464 $environment['TEST_TIMEOUT'] = $argv[++$i]; 465 break; 466 case '--show-all': 467 foreach ($cfgfiles as $file) { 468 $cfg['show'][$file] = true; 469 } 470 break; 471 case '--show-slow': 472 $slow_min_ms = $argv[++$i]; 473 break; 474 case '--temp-source': 475 $temp_source = $argv[++$i]; 476 break; 477 case '--temp-target': 478 $temp_target = $argv[++$i]; 479 if ($temp_urlbase) { 480 $temp_urlbase = $temp_target; 481 } 482 break; 483 case '--temp-urlbase': 484 $temp_urlbase = $argv[++$i]; 485 break; 486 case 'v': 487 case '--verbose': 488 $DETAILED = true; 489 break; 490 case 'x': 491 $environment['SKIP_SLOW_TESTS'] = 1; 492 break; 493 case '--offline': 494 $environment['SKIP_ONLINE_TESTS'] = 1; 495 break; 496 case '--shuffle': 497 $shuffle = true; 498 break; 499 case '--asan': 500 $environment['USE_ZEND_ALLOC'] = 0; 501 $environment['USE_TRACKED_ALLOC'] = 1; 502 $environment['SKIP_ASAN'] = 1; 503 $environment['SKIP_PERF_SENSITIVE'] = 1; 504 505 $lsanSuppressions = __DIR__ . '/azure/lsan-suppressions.txt'; 506 if (file_exists($lsanSuppressions)) { 507 $environment['LSAN_OPTIONS'] = 'suppressions=' . $lsanSuppressions 508 . ':print_suppressions=0'; 509 } 510 break; 511 //case 'w' 512 case '-': 513 // repeat check with full switch 514 $switch = $argv[$i]; 515 if ($switch != '-') { 516 $repeat = true; 517 } 518 break; 519 case '--html': 520 $html_file = fopen($argv[++$i], 'wt'); 521 $html_output = is_resource($html_file); 522 break; 523 case '--version': 524 echo '$Id: 19b7f5cb4015d00823d154fd0b256389c2d8d6cc $' . "\n"; 525 exit(1); 526 527 default: 528 echo "Illegal switch '$switch' specified!\n"; 529 case 'h': 530 case '-help': 531 case '--help': 532 echo <<<HELP 533Synopsis: 534 php run-tests.php [options] [files] [directories] 535 536Options: 537 -j<workers> Run <workers> simultaneous testing processes in parallel for 538 quicker testing on systems with multiple logical processors. 539 Note that this is experimental feature. 540 541 -l <file> Read the testfiles to be executed from <file>. After the test 542 has finished all failed tests are written to the same <file>. 543 If the list is empty and no further test is specified then 544 all tests are executed (same as: -r <file> -w <file>). 545 546 -r <file> Read the testfiles to be executed from <file>. 547 548 -w <file> Write a list of all failed tests to <file>. 549 550 -a <file> Same as -w but append rather then truncating <file>. 551 552 -W <file> Write a list of all tests and their result status to <file>. 553 554 -c <file> Look for php.ini in directory <file> or use <file> as ini. 555 556 -n Pass -n option to the php binary (Do not use a php.ini). 557 558 -d foo=bar Pass -d option to the php binary (Define INI entry foo 559 with value 'bar'). 560 561 -g Comma separated list of groups to show during test run 562 (possible values: PASS, FAIL, XFAIL, XLEAK, SKIP, BORK, WARN, LEAK, REDIRECT). 563 564 -m Test for memory leaks with Valgrind (equivalent to -M memcheck). 565 566 -M <tool> Test for errors with Valgrind tool. 567 568 -p <php> Specify PHP executable to run. 569 570 -P Use PHP_BINARY as PHP executable to run (default). 571 572 -q Quiet, no user interaction (same as environment NO_INTERACTION). 573 574 -s <file> Write output to <file>. 575 576 -x Sets 'SKIP_SLOW_TESTS' environmental variable. 577 578 --offline Sets 'SKIP_ONLINE_TESTS' environmental variable. 579 580 --verbose 581 -v Verbose mode. 582 583 --help 584 -h This Help. 585 586 --html <file> Generate HTML output. 587 588 --temp-source <sdir> --temp-target <tdir> [--temp-urlbase <url>] 589 Write temporary files to <tdir> by replacing <sdir> from the 590 filenames to generate with <tdir>. If --html is being used and 591 <url> given then the generated links are relative and prefixed 592 with the given url. In general you want to make <sdir> the path 593 to your source files and <tdir> some patch in your web page 594 hierarchy with <url> pointing to <tdir>. 595 596 --keep-[all|php|skip|clean] 597 Do not delete 'all' files, 'php' test file, 'skip' or 'clean' 598 file. 599 600 --set-timeout [n] 601 Set timeout for individual tests, where [n] is the number of 602 seconds. The default value is 60 seconds, or 300 seconds when 603 testing for memory leaks. 604 605 --show-[all|php|skip|clean|exp|diff|out|mem] 606 Show 'all' files, 'php' test file, 'skip' or 'clean' file. You 607 can also use this to show the output 'out', the expected result 608 'exp', the difference between them 'diff' or the valgrind log 609 'mem'. The result types get written independent of the log format, 610 however 'diff' only exists when a test fails. 611 612 --show-slow [n] 613 Show all tests that took longer than [n] milliseconds to run. 614 615 --no-clean Do not execute clean section if any. 616 617HELP; 618 exit(1); 619 } 620 } 621 622 if (!$is_switch) { 623 $testfile = realpath($argv[$i]); 624 625 if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) { 626 627 if (substr($argv[$i], -5) == '.phpt') { 628 $pattern_match = glob($argv[$i]); 629 } else { 630 if (preg_match("/\*$/", $argv[$i])) { 631 $pattern_match = glob($argv[$i] . '.phpt'); 632 } else { 633 die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); 634 } 635 } 636 637 if (is_array($pattern_match)) { 638 $test_files = array_merge($test_files, $pattern_match); 639 } 640 641 } else { 642 if (is_dir($testfile)) { 643 find_files($testfile); 644 } else { 645 if (substr($testfile, -5) == '.phpt') { 646 $test_files[] = $testfile; 647 } else { 648 die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); 649 } 650 } 651 } 652 } 653 } 654 655 // Default to PHP_BINARY as executable 656 if (!isset($environment['TEST_PHP_EXECUTABLE'])) { 657 $php = PHP_BINARY; 658 putenv("TEST_PHP_EXECUTABLE=$php"); 659 $environment['TEST_PHP_EXECUTABLE'] = $php; 660 } 661 662 if (strlen($conf_passed)) { 663 if (substr(PHP_OS, 0, 3) == "WIN") { 664 $pass_options .= " -c " . escapeshellarg($conf_passed); 665 } else { 666 $pass_options .= " -c '" . realpath($conf_passed) . "'"; 667 } 668 } 669 670 $test_files = array_unique($test_files); 671 $test_files = array_merge($test_files, $redir_tests); 672 673 // Run selected tests. 674 $test_cnt = count($test_files); 675 676 if ($test_cnt) { 677 putenv('NO_INTERACTION=1'); 678 verify_config(); 679 write_information(); 680 usort($test_files, "test_sort"); 681 $start_time = time(); 682 683 if (!$html_output) { 684 echo "Running selected tests.\n"; 685 } else { 686 show_start($start_time); 687 } 688 689 $test_idx = 0; 690 run_all_tests($test_files, $environment); 691 $end_time = time(); 692 693 if ($html_output) { 694 show_end($end_time); 695 } 696 697 if ($failed_tests_file) { 698 fclose($failed_tests_file); 699 } 700 701 if ($result_tests_file) { 702 fclose($result_tests_file); 703 } 704 705 compute_summary(); 706 if ($html_output) { 707 fwrite($html_file, "<hr/>\n" . get_summary(false, true)); 708 } 709 echo "====================================================================="; 710 echo get_summary(false, false); 711 712 if ($html_output) { 713 fclose($html_file); 714 } 715 716 if ($output_file != '' && $just_save_results) { 717 save_or_mail_results(); 718 } 719 720 junit_save_xml(); 721 722 if (getenv('REPORT_EXIT_STATUS') !== '0' && 723 getenv('REPORT_EXIT_STATUS') !== 'no' && ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['LEAKED'])) { 724 exit(1); 725 } 726 727 return; 728 } 729 730 verify_config(); 731 write_information(); 732 733 // Compile a list of all test files (*.phpt). 734 $test_files = array(); 735 $exts_tested = count($exts_to_test); 736 $exts_skipped = 0; 737 $ignored_by_ext = 0; 738 sort($exts_to_test); 739 $test_dirs = array(); 740 $optionals = array('Zend', 'tests', 'ext', 'sapi'); 741 742 foreach ($optionals as $dir) { 743 if (is_dir($dir)) { 744 $test_dirs[] = $dir; 745 } 746 } 747 748 // Convert extension names to lowercase 749 foreach ($exts_to_test as $key => $val) { 750 $exts_to_test[$key] = strtolower($val); 751 } 752 753 foreach ($test_dirs as $dir) { 754 find_files(TEST_PHP_SRCDIR . "/{$dir}", $dir == 'ext'); 755 } 756 757 foreach ($user_tests as $dir) { 758 find_files($dir, $dir == 'ext'); 759 } 760 761 $test_files = array_unique($test_files); 762 usort($test_files, "test_sort"); 763 764 $start_time = time(); 765 show_start($start_time); 766 767 $test_cnt = count($test_files); 768 $test_idx = 0; 769 run_all_tests($test_files, $environment); 770 $end_time = time(); 771 772 if ($failed_tests_file) { 773 fclose($failed_tests_file); 774 } 775 776 if ($result_tests_file) { 777 fclose($result_tests_file); 778 } 779 780 // Summarize results 781 782 if (0 == count($test_results)) { 783 echo "No tests were run.\n"; 784 return; 785 } 786 787 compute_summary(); 788 789 show_end($end_time); 790 show_summary(); 791 792 if ($html_output) { 793 fclose($html_file); 794 } 795 796 save_or_mail_results(); 797 798 junit_save_xml(); 799 if (getenv('REPORT_EXIT_STATUS') !== '0' && 800 getenv('REPORT_EXIT_STATUS') !== 'no' && ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['LEAKED'])) { 801 exit(1); 802 } 803 exit(0); 804} 805 806if (!function_exists("hrtime")) { 807 function hrtime(bool $as_num = false) 808 { 809 $t = microtime(true); 810 811 if ($as_num) { 812 return $t * 1000000000; 813 } 814 815 $s = floor($t); 816 return array(0 => $s, 1 => ($t - $s) * 1000000000); 817 } 818} 819 820function verify_config() 821{ 822 global $php; 823 824 if (empty($php) || !file_exists($php)) { 825 error('environment variable TEST_PHP_EXECUTABLE must be set to specify PHP executable!'); 826 } 827 828 if (!is_executable($php)) { 829 error("invalid PHP executable specified by TEST_PHP_EXECUTABLE = $php"); 830 } 831} 832 833function write_information() 834{ 835 global $php, $php_cgi, $phpdbg, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $valgrind, $no_file_cache; 836 837 // Get info from php 838 $info_file = __DIR__ . '/run-test-info.php'; 839 @unlink($info_file); 840 $php_info = '<?php echo " 841PHP_SAPI : " , PHP_SAPI , " 842PHP_VERSION : " , phpversion() , " 843ZEND_VERSION: " , zend_version() , " 844PHP_OS : " , PHP_OS , " - " , php_uname() , " 845INI actual : " , realpath(get_cfg_var("cfg_file_path")) , " 846More .INIs : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n","", php_ini_scanned_files()) : "** not determined **"); ?>'; 847 save_text($info_file, $php_info); 848 $info_params = array(); 849 settings2array($ini_overwrites, $info_params); 850 $info_params = settings2params($info_params); 851 $php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`; 852 define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`); 853 854 if ($php_cgi && $php != $php_cgi) { 855 $php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`; 856 $php_info_sep = "\n---------------------------------------------------------------------"; 857 $php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep"; 858 } else { 859 $php_cgi_info = ''; 860 } 861 862 if ($phpdbg) { 863 $phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`; 864 $php_info_sep = "\n---------------------------------------------------------------------"; 865 $phpdbg_info = "$php_info_sep\nPHP : $phpdbg $phpdbg_info$php_info_sep"; 866 } else { 867 $phpdbg_info = ''; 868 } 869 870 if (function_exists('opcache_invalidate')) { 871 opcache_invalidate($info_file, true); 872 } 873 @unlink($info_file); 874 875 // load list of enabled extensions 876 save_text($info_file, 877 '<?php echo str_replace("Zend OPcache", "opcache", implode(",", get_loaded_extensions())); ?>'); 878 $exts_to_test = explode(',', `$php $pass_options $info_params $no_file_cache "$info_file"`); 879 // check for extensions that need special handling and regenerate 880 $info_params_ex = array( 881 'session' => array('session.auto_start=0'), 882 'tidy' => array('tidy.clean_output=0'), 883 'zlib' => array('zlib.output_compression=Off'), 884 'xdebug' => array('xdebug.default_enable=0','xdebug.mode=off'), 885 'mbstring' => array('mbstring.func_overload=0'), 886 ); 887 888 foreach ($info_params_ex as $ext => $ini_overwrites_ex) { 889 if (in_array($ext, $exts_to_test)) { 890 $ini_overwrites = array_merge($ini_overwrites, $ini_overwrites_ex); 891 } 892 } 893 894 if (function_exists('opcache_invalidate')) { 895 opcache_invalidate($info_file, true); 896 } 897 @unlink($info_file); 898 899 // Write test context information. 900 echo " 901===================================================================== 902PHP : $php $php_info $php_cgi_info $phpdbg_info 903CWD : " . TEST_PHP_SRCDIR . " 904Extra dirs : "; 905 foreach ($user_tests as $test_dir) { 906 echo "{$test_dir}\n "; 907 } 908 echo " 909VALGRIND : " . ($valgrind ? $valgrind->getHeader() : 'Not used') . " 910===================================================================== 911"; 912} 913 914function save_or_mail_results() 915{ 916 global $sum_results, $just_save_results, $failed_test_summary, 917 $PHP_FAILED_TESTS, $php, $output_file; 918 919 /* We got failed Tests, offer the user to send an e-mail to QA team, unless NO_INTERACTION is set */ 920 if (!getenv('NO_INTERACTION') && !TRAVIS_CI) { 921 $fp = fopen("php://stdin", "r+"); 922 if ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['WARNED'] || $sum_results['LEAKED']) { 923 echo "\nYou may have found a problem in PHP."; 924 } 925 echo "\nThis report can be automatically sent to the PHP QA team at\n"; 926 echo QA_REPORTS_PAGE . " and http://news.php.net/php.qa.reports\n"; 927 echo "This gives us a better understanding of PHP's behavior.\n"; 928 echo "If you don't want to send the report immediately you can choose\n"; 929 echo "option \"s\" to save it. You can then email it to " . PHP_QA_EMAIL . " later.\n"; 930 echo "Do you want to send this report now? [Yns]: "; 931 flush(); 932 933 $user_input = fgets($fp, 10); 934 $just_save_results = (strtolower($user_input[0]) == 's'); 935 } 936 937 if ($just_save_results || !getenv('NO_INTERACTION') || TRAVIS_CI) { 938 if ($just_save_results || TRAVIS_CI || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') { 939 /* 940 * Collect information about the host system for our report 941 * Fetch phpinfo() output so that we can see the PHP environment 942 * Make an archive of all the failed tests 943 * Send an email 944 */ 945 if ($just_save_results) { 946 $user_input = 's'; 947 } 948 949 /* Ask the user to provide an email address, so that QA team can contact the user */ 950 if (TRAVIS_CI) { 951 $user_email = 'travis at php dot net'; 952 } elseif (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) { 953 echo "\nPlease enter your email address.\n(Your address will be mangled so that it will not go out on any\nmailinglist in plain text): "; 954 flush(); 955 $user_email = trim(fgets($fp, 1024)); 956 $user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email)); 957 } 958 959 $failed_tests_data = ''; 960 $sep = "\n" . str_repeat('=', 80) . "\n"; 961 $failed_tests_data .= $failed_test_summary . "\n"; 962 $failed_tests_data .= get_summary(true, false) . "\n"; 963 964 if ($sum_results['FAILED']) { 965 foreach ($PHP_FAILED_TESTS['FAILED'] as $test_info) { 966 $failed_tests_data .= $sep . $test_info['name'] . $test_info['info']; 967 $failed_tests_data .= $sep . file_get_contents(realpath($test_info['output']), FILE_BINARY); 968 $failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff']), FILE_BINARY); 969 $failed_tests_data .= $sep . "\n\n"; 970 } 971 $status = "failed"; 972 } else { 973 $status = "success"; 974 } 975 976 $failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep; 977 $failed_tests_data .= "OS:\n" . PHP_OS . " - " . php_uname() . "\n\n"; 978 $ldd = $autoconf = $sys_libtool = $libtool = $compiler = 'N/A'; 979 980 if (substr(PHP_OS, 0, 3) != "WIN") { 981 /* If PHP_AUTOCONF is set, use it; otherwise, use 'autoconf'. */ 982 if (getenv('PHP_AUTOCONF')) { 983 $autoconf = shell_exec(getenv('PHP_AUTOCONF') . ' --version'); 984 } else { 985 $autoconf = shell_exec('autoconf --version'); 986 } 987 988 /* Always use the generated libtool - Mac OSX uses 'glibtool' */ 989 $libtool = shell_exec(INIT_DIR . '/libtool --version'); 990 991 /* Use shtool to find out if there is glibtool present (MacOSX) */ 992 $sys_libtool_path = shell_exec(__DIR__ . '/build/shtool path glibtool libtool'); 993 994 if ($sys_libtool_path) { 995 $sys_libtool = shell_exec(str_replace("\n", "", $sys_libtool_path) . ' --version'); 996 } 997 998 /* Try the most common flags for 'version' */ 999 $flags = array('-v', '-V', '--version'); 1000 $cc_status = 0; 1001 1002 foreach ($flags as $flag) { 1003 system(getenv('CC') . " $flag >/dev/null 2>&1", $cc_status); 1004 if ($cc_status == 0) { 1005 $compiler = shell_exec(getenv('CC') . " $flag 2>&1"); 1006 break; 1007 } 1008 } 1009 1010 $ldd = shell_exec("ldd $php 2>/dev/null"); 1011 } 1012 1013 $failed_tests_data .= "Autoconf:\n$autoconf\n"; 1014 $failed_tests_data .= "Bundled Libtool:\n$libtool\n"; 1015 $failed_tests_data .= "System Libtool:\n$sys_libtool\n"; 1016 $failed_tests_data .= "Compiler:\n$compiler\n"; 1017 $failed_tests_data .= "Bison:\n" . shell_exec('bison --version 2>/dev/null') . "\n"; 1018 $failed_tests_data .= "Libraries:\n$ldd\n"; 1019 $failed_tests_data .= "\n"; 1020 1021 if (isset($user_email)) { 1022 $failed_tests_data .= "User's E-mail: " . $user_email . "\n\n"; 1023 } 1024 1025 $failed_tests_data .= $sep . "PHPINFO" . $sep; 1026 $failed_tests_data .= shell_exec($php . ' -ddisplay_errors=stderr -dhtml_errors=0 -i 2> /dev/null'); 1027 1028 if (($just_save_results || !mail_qa_team($failed_tests_data, $status)) && !TRAVIS_CI) { 1029 file_put_contents($output_file, $failed_tests_data); 1030 1031 if (!$just_save_results) { 1032 echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n"; 1033 } 1034 1035 echo "Please send " . $output_file . " to " . PHP_QA_EMAIL . " manually, thank you.\n"; 1036 } elseif (!getenv('NO_INTERACTION') && !TRAVIS_CI) { 1037 fwrite($fp, "\nThank you for helping to make PHP better.\n"); 1038 fclose($fp); 1039 } 1040 } 1041 } 1042} 1043 1044function find_files($dir, $is_ext_dir = false, $ignore = false) 1045{ 1046 global $test_files, $exts_to_test, $ignored_by_ext, $exts_skipped; 1047 1048 $o = opendir($dir) or error("cannot open directory: $dir"); 1049 1050 while (($name = readdir($o)) !== false) { 1051 1052 if (is_dir("{$dir}/{$name}") && !in_array($name, array('.', '..', '.svn'))) { 1053 $skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test)); 1054 if ($skip_ext) { 1055 $exts_skipped++; 1056 } 1057 find_files("{$dir}/{$name}", false, $ignore || $skip_ext); 1058 } 1059 1060 // Cleanup any left-over tmp files from last run. 1061 if (substr($name, -4) == '.tmp') { 1062 @unlink("$dir/$name"); 1063 continue; 1064 } 1065 1066 // Otherwise we're only interested in *.phpt files. 1067 if (substr($name, -5) == '.phpt') { 1068 if ($ignore) { 1069 $ignored_by_ext++; 1070 } else { 1071 $testfile = realpath("{$dir}/{$name}"); 1072 $test_files[] = $testfile; 1073 } 1074 } 1075 } 1076 1077 closedir($o); 1078} 1079 1080function test_name($name) 1081{ 1082 if (is_array($name)) { 1083 return $name[0] . ':' . $name[1]; 1084 } else { 1085 return $name; 1086 } 1087} 1088 1089function test_sort($a, $b) 1090{ 1091 $a = test_name($a); 1092 $b = test_name($b); 1093 1094 $ta = strpos($a, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($a, 1095 TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0; 1096 $tb = strpos($b, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($b, 1097 TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0; 1098 1099 if ($ta == $tb) { 1100 return strcmp($a, $b); 1101 } else { 1102 return $tb - $ta; 1103 } 1104} 1105 1106 1107 1108// 1109// Send Email to QA Team 1110// 1111 1112function mail_qa_team($data, $status = false) 1113{ 1114 $url_bits = parse_url(QA_SUBMISSION_PAGE); 1115 1116 if ($proxy = getenv('http_proxy')) { 1117 $proxy = parse_url($proxy); 1118 $path = $url_bits['host'] . $url_bits['path']; 1119 $host = $proxy['host']; 1120 if (empty($proxy['port'])) { 1121 $proxy['port'] = 80; 1122 } 1123 $port = $proxy['port']; 1124 } else { 1125 $path = $url_bits['path']; 1126 $host = $url_bits['host']; 1127 $port = empty($url_bits['port']) ? 80 : $port = $url_bits['port']; 1128 } 1129 1130 $data = "php_test_data=" . urlencode(base64_encode(str_replace("\00", '[0x0]', $data))); 1131 $data_length = strlen($data); 1132 1133 $fs = fsockopen($host, $port, $errno, $errstr, 10); 1134 1135 if (!$fs) { 1136 return false; 1137 } 1138 1139 $php_version = urlencode(TESTED_PHP_VERSION); 1140 1141 echo "\nPosting to " . QA_SUBMISSION_PAGE . "\n"; 1142 fwrite($fs, "POST " . $path . "?status=$status&version=$php_version HTTP/1.1\r\n"); 1143 fwrite($fs, "Host: " . $host . "\r\n"); 1144 fwrite($fs, "User-Agent: QA Browser 0.1\r\n"); 1145 fwrite($fs, "Content-Type: application/x-www-form-urlencoded\r\n"); 1146 fwrite($fs, "Content-Length: " . $data_length . "\r\n\r\n"); 1147 fwrite($fs, $data); 1148 fwrite($fs, "\r\n\r\n"); 1149 fclose($fs); 1150 1151 return 1; 1152} 1153 1154 1155// 1156// Write the given text to a temporary file, and return the filename. 1157// 1158 1159function save_text($filename, $text, $filename_copy = null) 1160{ 1161 global $DETAILED; 1162 1163 if ($filename_copy && $filename_copy != $filename) { 1164 if (file_put_contents($filename_copy, $text, FILE_BINARY) === false) { 1165 error("Cannot open file '" . $filename_copy . "' (save_text)"); 1166 } 1167 } 1168 1169 if (file_put_contents($filename, $text, FILE_BINARY) === false) { 1170 error("Cannot open file '" . $filename . "' (save_text)"); 1171 } 1172 1173 if (1 < $DETAILED) echo " 1174FILE $filename {{{ 1175$text 1176}}} 1177"; 1178} 1179 1180// 1181// Write an error in a format recognizable to Emacs or MSVC. 1182// 1183 1184function error_report($testname, $logname, $tested) 1185{ 1186 $testname = realpath($testname); 1187 $logname = realpath($logname); 1188 1189 switch (strtoupper(getenv('TEST_PHP_ERROR_STYLE'))) { 1190 case 'MSVC': 1191 echo $testname . "(1) : $tested\n"; 1192 echo $logname . "(1) : $tested\n"; 1193 break; 1194 case 'EMACS': 1195 echo $testname . ":1: $tested\n"; 1196 echo $logname . ":1: $tested\n"; 1197 break; 1198 } 1199} 1200 1201function system_with_timeout($commandline, $env = null, $stdin = null, $captureStdIn = true, $captureStdOut = true, $captureStdErr = true) 1202{ 1203 global $valgrind; 1204 1205 $data = ''; 1206 1207 $bin_env = array(); 1208 foreach ((array)$env as $key => $value) { 1209 $bin_env[$key] = $value; 1210 } 1211 1212 $descriptorspec = array(); 1213 if ($captureStdIn) { 1214 $descriptorspec[0] = array('pipe', 'r'); 1215 } 1216 if ($captureStdOut) { 1217 $descriptorspec[1] = array('pipe', 'w'); 1218 } 1219 if ($captureStdErr) { 1220 $descriptorspec[2] = array('pipe', 'w'); 1221 } 1222 $proc = proc_open($commandline, $descriptorspec, $pipes, TEST_PHP_SRCDIR, $bin_env, array('suppress_errors' => true)); 1223 1224 if (!$proc) { 1225 return false; 1226 } 1227 1228 if ($captureStdIn) { 1229 if (!is_null($stdin)) { 1230 fwrite($pipes[0], $stdin); 1231 } 1232 fclose($pipes[0]); 1233 unset($pipes[0]); 1234 } 1235 1236 $timeout = $valgrind ? 300 : ($env['TEST_TIMEOUT'] ?? 60); 1237 1238 while (true) { 1239 /* hide errors from interrupted syscalls */ 1240 $r = $pipes; 1241 $w = null; 1242 $e = null; 1243 1244 $n = @stream_select($r, $w, $e, $timeout); 1245 1246 if ($n === false) { 1247 break; 1248 } else if ($n === 0) { 1249 /* timed out */ 1250 $data .= "\n ** ERROR: process timed out **\n"; 1251 proc_terminate($proc, 9); 1252 return $data; 1253 } else if ($n > 0) { 1254 if ($captureStdOut) { 1255 $line = fread($pipes[1], 8192); 1256 } elseif ($captureStdErr) { 1257 $line = fread($pipes[2], 8192); 1258 } else { 1259 $line = ''; 1260 } 1261 if (strlen($line) == 0) { 1262 /* EOF */ 1263 break; 1264 } 1265 $data .= $line; 1266 } 1267 } 1268 1269 $stat = proc_get_status($proc); 1270 1271 if ($stat['signaled']) { 1272 $data .= "\nTermsig=" . $stat['stopsig'] . "\n"; 1273 } 1274 if ($stat["exitcode"] > 128 && $stat["exitcode"] < 160) { 1275 $data .= "\nTermsig=" . ($stat["exitcode"] - 128) . "\n"; 1276 } else if (defined('PHP_WINDOWS_VERSION_MAJOR') && (($stat["exitcode"] >> 28) & 0b1111) === 0b1100) { 1277 // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/87fba13e-bf06-450e-83b1-9241dc81e781 1278 $data .= "\nTermsig=" . $stat["exitcode"] . "\n"; 1279 } 1280 1281 proc_close($proc); 1282 return $data; 1283} 1284 1285function run_all_tests($test_files, $env, $redir_tested = null) 1286{ 1287 global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx; 1288 // Parallel testing 1289 global $PHP_FAILED_TESTS, $workers, $workerID, $workerSock; 1290 1291 if ($workers !== null && !$workerID) { 1292 run_all_tests_parallel($test_files, $env, $redir_tested); 1293 return; 1294 } 1295 1296 foreach ($test_files as $name) { 1297 if (is_array($name)) { 1298 $index = "# $name[1]: $name[0]"; 1299 1300 if ($redir_tested) { 1301 $name = $name[0]; 1302 } 1303 } else if ($redir_tested) { 1304 $index = "# $redir_tested: $name"; 1305 } else { 1306 $index = $name; 1307 } 1308 $test_idx++; 1309 1310 if ($workerID) { 1311 $PHP_FAILED_TESTS = ['BORKED' => [], 'FAILED' => [], 'WARNED' => [], 'LEAKED' => [], 'XFAILED' => [], 'XLEAKED' => [], 'SLOW' => []]; 1312 ob_start(); 1313 } 1314 1315 $result = run_test($php, $name, $env); 1316 if ($workerID) { 1317 $resultText = ob_get_clean(); 1318 } 1319 1320 if (!is_array($name) && $result != 'REDIR') { 1321 if ($workerID) { 1322 send_message($workerSock, [ 1323 "type" => "test_result", 1324 "name" => $name, 1325 "index" => $index, 1326 "result" => $result, 1327 "text" => $resultText, 1328 "PHP_FAILED_TESTS" => $PHP_FAILED_TESTS 1329 ]); 1330 continue; 1331 } 1332 1333 $test_results[$index] = $result; 1334 if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { 1335 fwrite($failed_tests_file, "$index\n"); 1336 } 1337 if ($result_tests_file) { 1338 fwrite($result_tests_file, "$result\t$index\n"); 1339 } 1340 } 1341 } 1342} 1343 1344/** The heart of parallel testing. */ 1345function run_all_tests_parallel($test_files, $env, $redir_tested) { 1346 global $workers, $test_idx, $test_cnt, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $SHOW_ONLY_GROUPS; 1347 1348 // The PHP binary running run-tests.php, and run-tests.php itself 1349 // This PHP executable is *not* necessarily the same as the tested version 1350 $thisPHP = PHP_BINARY; 1351 $thisScript = __FILE__; 1352 1353 $workerProcs = []; 1354 $workerSocks = []; 1355 1356 echo "=====================================================================\n"; 1357 echo "========= WELCOME TO THE FUTURE: run-tests PARALLEL EDITION =========\n"; 1358 echo "=====================================================================\n"; 1359 1360 // Each test may specify a list of conflict keys. While a test that conflicts with 1361 // key K is running, no other test that conflicts with K may run. Conflict keys are 1362 // specified either in the --CONFLICTS-- section, or CONFLICTS file inside a directory. 1363 $dirConflictsWith = []; 1364 $fileConflictsWith = []; 1365 $sequentialTests = []; 1366 foreach ($test_files as $i => $file) { 1367 $contents = file_get_contents($file); 1368 if (preg_match('/^--CONFLICTS--(.+?)^--/ms', $contents, $matches)) { 1369 $conflicts = parse_conflicts($matches[1]); 1370 } else { 1371 // Cache per-directory conflicts in a separate map, so we compute these only once. 1372 $dir = dirname($file); 1373 if (!isset($dirConflictsWith[$dir])) { 1374 $dirConflicts = []; 1375 if (file_exists($dir . '/CONFLICTS')) { 1376 $contents = file_get_contents($dir . '/CONFLICTS'); 1377 $dirConflicts = parse_conflicts($contents); 1378 } 1379 $dirConflictsWith[$dir] = $dirConflicts; 1380 } 1381 $conflicts = $dirConflictsWith[$dir]; 1382 } 1383 1384 // For tests conflicting with "all", no other tests may run in parallel. We'll run these 1385 // tests separately at the end, when only one worker is left. 1386 if (in_array('all', $conflicts, true)) { 1387 $sequentialTests[] = $file; 1388 unset($test_files[$i]); 1389 } 1390 1391 $fileConflictsWith[$file] = $conflicts; 1392 } 1393 1394 // Some tests assume that they are executed in a certain order. We will be popping from 1395 // $test_files, so reverse its order here. This makes sure that order is preserved at least 1396 // for tests with a common conflict key. 1397 $test_files = array_reverse($test_files); 1398 1399 // To discover parallelization issues it is useful to randomize the test order. 1400 if ($shuffle) { 1401 shuffle($test_files); 1402 } 1403 1404 echo "Spawning workers… "; 1405 1406 // We use sockets rather than STDIN/STDOUT for comms because on Windows, 1407 // those can't be non-blocking for some reason. 1408 $listenSock = stream_socket_server("tcp://127.0.0.1:0") or error("Couldn't create socket on localhost."); 1409 $sockName = stream_socket_get_name($listenSock, false); 1410 // PHP is terrible and returns IPv6 addresses not enclosed by [] 1411 $portPos = strrpos($sockName, ":"); 1412 $sockHost = substr($sockName, 0, $portPos); 1413 if (FALSE !== strpos($sockHost, ":")) { 1414 $sockHost = "[$sockHost]"; 1415 } 1416 $sockPort = substr($sockName, $portPos + 1); 1417 $sockUri = "tcp://$sockHost:$sockPort"; 1418 1419 for ($i = 1; $i <= $workers; $i++) { 1420 $proc = proc_open( 1421 $thisPHP . ' ' . escapeshellarg($thisScript), 1422 [], // Inherit our stdin, stdout and stderr 1423 $pipes, 1424 NULL, 1425 $_ENV + [ 1426 "TEST_PHP_WORKER" => $i, 1427 "TEST_PHP_URI" => $sockUri, 1428 ], 1429 [ 1430 "suppress_errors" => TRUE, 1431 'create_new_console' => TRUE, 1432 ] 1433 ); 1434 if ($proc === FALSE) { 1435 kill_children($workerProcs); 1436 error("Failed to spawn worker $i"); 1437 } 1438 $workerProcs[$i] = $proc; 1439 1440 $workerSock = stream_socket_accept($listenSock, 5); 1441 if ($workerSock === FALSE) { 1442 kill_children($workerProcs); 1443 error("Failed to accept connection from worker $i"); 1444 } 1445 1446 $greeting = base64_encode(serialize([ 1447 "type" => "hello", 1448 "workerID" => $i, 1449 "GLOBALS" => $GLOBALS, 1450 "constants" => [ 1451 "INIT_DIR" => INIT_DIR, 1452 "TEST_PHP_SRCDIR" => TEST_PHP_SRCDIR, 1453 "PHP_QA_EMAIL" => PHP_QA_EMAIL, 1454 "QA_SUBMISSION_PAGE" => QA_SUBMISSION_PAGE, 1455 "QA_REPORTS_PAGE" => QA_REPORTS_PAGE, 1456 "TRAVIS_CI" => TRAVIS_CI 1457 ] 1458 ])) . "\n"; 1459 1460 stream_set_timeout($workerSock, 5); 1461 if (fwrite($workerSock, $greeting) === FALSE) { 1462 kill_children($workerProcs); 1463 error("Failed to send greeting to worker $i."); 1464 } 1465 1466 $rawReply = fgets($workerSock); 1467 if ($rawReply === FALSE) { 1468 kill_children($workerProcs); 1469 error("Failed to read greeting reply from worker $i."); 1470 } 1471 1472 $reply = unserialize(base64_decode($rawReply)); 1473 if (!$reply || $reply["type"] !== "hello_reply" || $reply["workerID"] !== $i) { 1474 kill_children($workerProcs); 1475 error("Greeting reply from worker $i unexpected or could not be decoded: '$rawReply'"); 1476 } 1477 1478 stream_set_timeout($workerSock, 0); 1479 stream_set_blocking($workerSock, FALSE); 1480 1481 $workerSocks[$i] = $workerSock; 1482 1483 echo "$i "; 1484 } 1485 echo "… done!\n"; 1486 echo "=====================================================================\n"; 1487 echo "\n"; 1488 1489 $rawMessageBuffers = []; 1490 $testsInProgress = 0; 1491 1492 // Map from conflict key to worker ID. 1493 $activeConflicts = []; 1494 // Tests waiting due to conflicts. Map from conflict key to array. 1495 $waitingTests = []; 1496 1497escape: 1498 while ($test_files || $sequentialTests || $testsInProgress > 0) { 1499 $toRead = array_values($workerSocks); 1500 $toWrite = NULL; 1501 $toExcept = NULL; 1502 if (stream_select($toRead, $toWrite, $toExcept, 10)) { 1503 foreach ($toRead as $workerSock) { 1504 $i = array_search($workerSock, $workerSocks); 1505 if ($i === FALSE) { 1506 kill_children($workerProcs); 1507 error("Could not find worker stdout in array of worker stdouts, THIS SHOULD NOT HAPPEN."); 1508 } 1509 while (FALSE !== ($rawMessage = fgets($workerSock))) { 1510 // work around fgets truncating things 1511 if (($rawMessageBuffers[$i] ?? '') !== '') { 1512 $rawMessage = $rawMessageBuffers[$i] . $rawMessage; 1513 $rawMessageBuffers[$i] = ''; 1514 } 1515 if (substr($rawMessage, -1) !== "\n") { 1516 $rawMessageBuffers[$i] = $rawMessage; 1517 continue; 1518 } 1519 1520 $message = unserialize(base64_decode($rawMessage)); 1521 if (!$message) { 1522 kill_children($workerProcs); 1523 $stuff = fread($workerSock, 65536); 1524 error("Could not decode message from worker $i: '$rawMessage$stuff'"); 1525 } 1526 1527 switch ($message["type"]) { 1528 case "tests_finished": 1529 $testsInProgress--; 1530 foreach ($activeConflicts as $key => $workerId) { 1531 if ($workerId === $i) { 1532 unset($activeConflicts[$key]); 1533 if (isset($waitingTests[$key])) { 1534 while ($test = array_pop($waitingTests[$key])) { 1535 $test_files[] = $test; 1536 } 1537 unset($waitingTests[$key]); 1538 } 1539 } 1540 } 1541 if (junit_enabled()) { 1542 junit_merge_results($message["junit"]); 1543 } 1544 // intentional fall-through 1545 case "ready": 1546 // Schedule sequential tests only once we are down to one worker. 1547 if (count($workerProcs) === 1 && $sequentialTests) { 1548 $test_files = array_merge($test_files, $sequentialTests); 1549 $sequentialTests = []; 1550 } 1551 // Batch multiple tests to reduce communication overhead. 1552 $files = []; 1553 $batchSize = $shuffle ? 4 : 32; 1554 while (count($files) <= $batchSize && $file = array_pop($test_files)) { 1555 foreach ($fileConflictsWith[$file] as $conflictKey) { 1556 if (isset($activeConflicts[$conflictKey])) { 1557 $waitingTests[$conflictKey][] = $file; 1558 continue 2; 1559 } 1560 } 1561 $files[] = $file; 1562 } 1563 if ($files) { 1564 foreach ($files as $file) { 1565 foreach ($fileConflictsWith[$file] as $conflictKey) { 1566 $activeConflicts[$conflictKey] = $i; 1567 } 1568 } 1569 $testsInProgress++; 1570 send_message($workerSocks[$i], [ 1571 "type" => "run_tests", 1572 "test_files" => $files, 1573 "env" => $env, 1574 "redir_tested" => $redir_tested 1575 ]); 1576 } else { 1577 proc_terminate($workerProcs[$i]); 1578 unset($workerProcs[$i]); 1579 unset($workerSocks[$i]); 1580 goto escape; 1581 } 1582 break; 1583 case "test_result": 1584 list($name, $index, $result, $resultText) = [$message["name"], $message["index"], $message["result"], $message["text"]]; 1585 foreach ($message["PHP_FAILED_TESTS"] as $category => $tests) { 1586 $PHP_FAILED_TESTS[$category] = array_merge($PHP_FAILED_TESTS[$category], $tests); 1587 } 1588 $test_idx++; 1589 1590 if (!$SHOW_ONLY_GROUPS) { 1591 clear_show_test(); 1592 } 1593 1594 echo $resultText; 1595 1596 if (!$SHOW_ONLY_GROUPS) { 1597 show_test($test_idx, count($workerProcs) . "/$workers concurrent test workers running"); 1598 } 1599 1600 if (!is_array($name) && $result != 'REDIR') { 1601 $test_results[$index] = $result; 1602 1603 if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { 1604 fwrite($failed_tests_file, "$index\n"); 1605 } 1606 if ($result_tests_file) { 1607 fwrite($result_tests_file, "$result\t$index\n"); 1608 } 1609 } 1610 break; 1611 case "error": 1612 kill_children($workerProcs); 1613 error("Worker $i reported error: $message[msg]"); 1614 break; 1615 case "php_error": 1616 kill_children($workerProcs); 1617 $error_consts = [ 1618 'E_ERROR', 1619 'E_WARNING', 1620 'E_PARSE', 1621 'E_NOTICE', 1622 'E_CORE_ERROR', 1623 'E_CORE_WARNING', 1624 'E_COMPILE_ERROR', 1625 'E_COMPILE_WARNING', 1626 'E_USER_ERROR', 1627 'E_USER_WARNING', 1628 'E_USER_NOTICE', 1629 'E_STRICT', // TODO Cleanup when removed from Zend Engine. 1630 'E_RECOVERABLE_ERROR', 1631 'E_USER_DEPRECATED' 1632 ]; 1633 $error_consts = array_combine(array_map('constant', $error_consts), $error_consts); 1634 error("Worker $i reported unexpected {$error_consts[$message['errno']]}: $message[errstr] in $message[errfile] on line $message[errline]"); 1635 default: 1636 kill_children($workerProcs); 1637 error("Unrecognised message type '$message[type]' from worker $i"); 1638 } 1639 } 1640 } 1641 } 1642 } 1643 1644 if (!$SHOW_ONLY_GROUPS) { 1645 clear_show_test(); 1646 } 1647 1648 kill_children($workerProcs); 1649 1650 if ($testsInProgress < 0) { 1651 error("$testsInProgress test batches “in progress”, which is less than zero. THIS SHOULD NOT HAPPEN."); 1652 } 1653} 1654 1655function send_message($stream, array $message) { 1656 $blocking = stream_get_meta_data($stream)["blocked"]; 1657 stream_set_blocking($stream, true); 1658 fwrite($stream, base64_encode(serialize($message)) . "\n"); 1659 stream_set_blocking($stream, $blocking); 1660} 1661 1662function kill_children(array $children) { 1663 foreach ($children as $child) { 1664 if ($child) { 1665 proc_terminate($child); 1666 } 1667 } 1668} 1669 1670function run_worker() { 1671 global $workerID, $workerSock; 1672 1673 $sockUri = getenv("TEST_PHP_URI"); 1674 1675 $workerSock = stream_socket_client($sockUri, $_, $_, 5) or error("Couldn't connect to $sockUri"); 1676 1677 $greeting = fgets($workerSock); 1678 $greeting = unserialize(base64_decode($greeting)) or die("Could not decode greeting\n"); 1679 if ($greeting["type"] !== "hello" || $greeting["workerID"] !== $workerID) { 1680 error("Unexpected greeting of type $greeting[type] and for worker $greeting[workerID]"); 1681 } 1682 1683 set_error_handler(function ($errno, $errstr, $errfile, $errline) use ($workerSock) { 1684 if (error_reporting() & $errno) { 1685 send_message($workerSock, compact('errno', 'errstr', 'errfile', 'errline') + [ 1686 'type' => 'php_error' 1687 ]); 1688 } 1689 1690 return true; 1691 }); 1692 1693 foreach ($greeting["GLOBALS"] as $var => $value) { 1694 if ($var !== "workerID" && $var !== "workerSock" && $var !== "GLOBALS") { 1695 $GLOBALS[$var] = $value; 1696 } 1697 } 1698 foreach ($greeting["constants"] as $const => $value) { 1699 define($const, $value); 1700 } 1701 1702 send_message($workerSock, [ 1703 "type" => "hello_reply", 1704 "workerID" => $workerID 1705 ]); 1706 1707 send_message($workerSock, [ 1708 "type" => "ready" 1709 ]); 1710 1711 while (($command = fgets($workerSock))) { 1712 $command = unserialize(base64_decode($command)); 1713 1714 switch ($command["type"]) { 1715 case "run_tests": 1716 run_all_tests($command["test_files"], $command["env"], $command["redir_tested"]); 1717 send_message($workerSock, [ 1718 "type" => "tests_finished", 1719 "junit" => junit_enabled() ? $GLOBALS['JUNIT'] : null, 1720 ]); 1721 junit_init(); 1722 break; 1723 default: 1724 send_message($workerSock, [ 1725 "type" => "error", 1726 "msg" => "Unrecognised message type: $command[type]" 1727 ]); 1728 break 2; 1729 } 1730 } 1731} 1732 1733// 1734// Show file or result block 1735// 1736function show_file_block($file, $block, $section = null) 1737{ 1738 global $cfg; 1739 1740 if ($cfg['show'][$file]) { 1741 1742 if (is_null($section)) { 1743 $section = strtoupper($file); 1744 } 1745 1746 echo "\n========" . $section . "========\n"; 1747 echo rtrim($block); 1748 echo "\n========DONE========\n"; 1749 } 1750} 1751 1752// 1753// Run an individual test case. 1754// 1755function run_test($php, $file, $env) 1756{ 1757 global $log_format, $ini_overwrites, $PHP_FAILED_TESTS; 1758 global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx; 1759 global $valgrind, $temp_source, $temp_target, $cfg, $environment; 1760 global $no_clean; 1761 global $SHOW_ONLY_GROUPS; 1762 global $no_file_cache; 1763 global $slow_min_ms; 1764 global $preload; 1765 // Parallel testing 1766 global $workerID; 1767 $temp_filenames = null; 1768 $org_file = $file; 1769 1770 if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) { 1771 $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE']; 1772 } 1773 1774 if (isset($env['TEST_PHPDBG_EXECUTABLE'])) { 1775 $phpdbg = $env['TEST_PHPDBG_EXECUTABLE']; 1776 } 1777 1778 if (is_array($file)) { 1779 $file = $file[0]; 1780 } 1781 1782 if ($DETAILED) echo " 1783================= 1784TEST $file 1785"; 1786 1787 // Load the sections of the test file. 1788 $section_text = array('TEST' => ''); 1789 1790 $fp = fopen($file, "rb") or error("Cannot open test file: $file"); 1791 1792 $bork_info = null; 1793 1794 if (!feof($fp)) { 1795 $line = fgets($fp); 1796 1797 if ($line === false) { 1798 $bork_info = "cannot read test"; 1799 } 1800 } else { 1801 $bork_info = "empty test [$file]"; 1802 } 1803 if ($bork_info === null && strncmp('--TEST--', $line, 8)) { 1804 $bork_info = "tests must start with --TEST-- [$file]"; 1805 } 1806 1807 $section = 'TEST'; 1808 $secfile = false; 1809 $secdone = false; 1810 1811 while (!feof($fp)) { 1812 $line = fgets($fp); 1813 1814 if ($line === false) { 1815 break; 1816 } 1817 1818 // Match the beginning of a section. 1819 if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { 1820 $section = (string)$r[1]; 1821 1822 if (isset($section_text[$section]) && $section_text[$section]) { 1823 $bork_info = "duplicated $section section"; 1824 } 1825 1826 // check for unknown sections 1827 if (!in_array($section, array( 1828 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', 1829 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', 1830 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', 1831 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', 1832 'INI', 'ENV', 'EXTENSIONS', 1833 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', 1834 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', 1835 ))) { 1836 $bork_info = 'Unknown section "' . $section . '"'; 1837 } 1838 1839 $section_text[$section] = ''; 1840 $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL'; 1841 $secdone = false; 1842 continue; 1843 } 1844 1845 // Add to the section text. 1846 if (!$secdone) { 1847 $section_text[$section] .= $line; 1848 } 1849 1850 // End of actual test? 1851 if ($secfile && preg_match('/^===DONE===\s*$/', $line)) { 1852 $secdone = true; 1853 } 1854 } 1855 1856 // the redirect section allows a set of tests to be reused outside of 1857 // a given test dir 1858 if ($bork_info === null) { 1859 if (isset($section_text['REDIRECTTEST'])) { 1860 1861 if ($IN_REDIRECT) { 1862 $bork_info = "Can't redirect a test from within a redirected test"; 1863 } 1864 1865 } else { 1866 1867 if (!isset($section_text['PHPDBG']) && isset($section_text['FILE']) + isset($section_text['FILEEOF']) + isset($section_text['FILE_EXTERNAL']) != 1) { 1868 $bork_info = "missing section --FILE--"; 1869 } 1870 1871 if (isset($section_text['FILEEOF'])) { 1872 $section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']); 1873 unset($section_text['FILEEOF']); 1874 } 1875 1876 foreach (array('FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) { 1877 $key = $prefix . '_EXTERNAL'; 1878 1879 if (isset($section_text[$key])) { 1880 // don't allow tests to retrieve files from anywhere but this subdirectory 1881 $section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key])); 1882 1883 if (file_exists($section_text[$key])) { 1884 $section_text[$prefix] = file_get_contents($section_text[$key], FILE_BINARY); 1885 unset($section_text[$key]); 1886 } else { 1887 $bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]); 1888 } 1889 } 1890 } 1891 1892 if ((isset($section_text['EXPECT']) + isset($section_text['EXPECTF']) + isset($section_text['EXPECTREGEX'])) != 1) { 1893 $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"; 1894 } 1895 } 1896 } 1897 fclose($fp); 1898 1899 $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); 1900 $tested_file = $shortname; 1901 1902 if ($bork_info !== null) { 1903 show_result("BORK", $bork_info, $tested_file); 1904 $PHP_FAILED_TESTS['BORKED'][] = array( 1905 'name' => $file, 1906 'test_name' => '', 1907 'output' => '', 1908 'diff' => '', 1909 'info' => "$bork_info [$file]", 1910 ); 1911 1912 junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info); 1913 return 'BORKED'; 1914 } 1915 1916 if (isset($section_text['CAPTURE_STDIO'])) { 1917 $captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false; 1918 $captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false; 1919 $captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false; 1920 } else { 1921 $captureStdIn = true; 1922 $captureStdOut = true; 1923 $captureStdErr = true; 1924 } 1925 if ($captureStdOut && $captureStdErr) { 1926 $cmdRedirect = ' 2>&1'; 1927 } else { 1928 $cmdRedirect = ''; 1929 } 1930 1931 $tested = trim($section_text['TEST']); 1932 1933 /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ 1934 if (array_key_exists('CGI', $section_text) || !empty($section_text['GET']) || !empty($section_text['POST']) || !empty($section_text['GZIP_POST']) || !empty($section_text['DEFLATE_POST']) || !empty($section_text['POST_RAW']) || !empty($section_text['PUT']) || !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { 1935 if (isset($php_cgi)) { 1936 $php = $php_cgi . ' -C '; 1937 } else if (!strncasecmp(PHP_OS, "win", 3) && file_exists(dirname($php) . "/php-cgi.exe")) { 1938 $php = realpath(dirname($php) . "/php-cgi.exe") . ' -C '; 1939 } else { 1940 if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) { 1941 $php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C '; 1942 } else if (file_exists("./sapi/cgi/php-cgi")) { 1943 $php = realpath("./sapi/cgi/php-cgi") . ' -C '; 1944 } else if (file_exists(dirname($php) . "/php-cgi")) { 1945 $php = realpath(dirname($php) . "/php-cgi") . ' -C '; 1946 } else { 1947 show_result('SKIP', $tested, $tested_file, "reason: CGI not available"); 1948 1949 junit_init_suite(junit_get_suitename_for($shortname)); 1950 junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available'); 1951 return 'SKIPPED'; 1952 } 1953 } 1954 $uses_cgi = true; 1955 } 1956 1957 /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */ 1958 $extra_options = ''; 1959 if (array_key_exists('PHPDBG', $section_text)) { 1960 if (!isset($section_text['STDIN'])) { 1961 $section_text['STDIN'] = $section_text['PHPDBG'] . "\n"; 1962 } 1963 1964 if (isset($phpdbg)) { 1965 $php = $phpdbg . ' -qIb'; 1966 1967 // Additional phpdbg command line options for sections that need to 1968 // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN. 1969 $extra_options = '-rr'; 1970 } else { 1971 show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available"); 1972 1973 junit_init_suite(junit_get_suitename_for($shortname)); 1974 junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available'); 1975 return 'SKIPPED'; 1976 } 1977 } 1978 1979 if (!$SHOW_ONLY_GROUPS && !$workerID) { 1980 show_test($test_idx, $shortname); 1981 } 1982 1983 if (is_array($IN_REDIRECT)) { 1984 $temp_dir = $test_dir = $IN_REDIRECT['dir']; 1985 } else { 1986 $temp_dir = $test_dir = realpath(dirname($file)); 1987 } 1988 1989 if ($temp_source && $temp_target) { 1990 $temp_dir = str_replace($temp_source, $temp_target, $temp_dir); 1991 } 1992 1993 $main_file_name = basename($file, 'phpt'); 1994 1995 $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff'; 1996 $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log'; 1997 $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp'; 1998 $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out'; 1999 $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem'; 2000 $sh_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh'; 2001 $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; 2002 $test_file = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; 2003 $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; 2004 $test_skipif = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; 2005 $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; 2006 $test_clean = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; 2007 $preload_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'preload.php'; 2008 $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'post'; 2009 $tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't'; 2010 2011 if ($temp_source && $temp_target) { 2012 $temp_skipif .= 's'; 2013 $temp_file .= 's'; 2014 $temp_clean .= 's'; 2015 $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps'; 2016 2017 if (!is_dir(dirname($copy_file))) { 2018 mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file)); 2019 } 2020 2021 if (isset($section_text['FILE'])) { 2022 save_text($copy_file, $section_text['FILE']); 2023 } 2024 2025 $temp_filenames = array( 2026 'file' => $copy_file, 2027 'diff' => $diff_filename, 2028 'log' => $log_filename, 2029 'exp' => $exp_filename, 2030 'out' => $output_filename, 2031 'mem' => $memcheck_filename, 2032 'sh' => $sh_filename, 2033 'php' => $temp_file, 2034 'skip' => $temp_skipif, 2035 'clean' => $temp_clean 2036 ); 2037 } 2038 2039 if (is_array($IN_REDIRECT)) { 2040 $tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']); 2041 $tested_file = $tmp_relative_file; 2042 $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $tested_file); 2043 } 2044 2045 // unlink old test results 2046 @unlink($diff_filename); 2047 @unlink($log_filename); 2048 @unlink($exp_filename); 2049 @unlink($output_filename); 2050 @unlink($memcheck_filename); 2051 @unlink($sh_filename); 2052 @unlink($temp_file); 2053 @unlink($test_file); 2054 @unlink($temp_skipif); 2055 @unlink($test_skipif); 2056 @unlink($tmp_post); 2057 @unlink($temp_clean); 2058 @unlink($test_clean); 2059 @unlink($preload_filename); 2060 2061 // Reset environment from any previous test. 2062 $env['REDIRECT_STATUS'] = ''; 2063 $env['QUERY_STRING'] = ''; 2064 $env['PATH_TRANSLATED'] = ''; 2065 $env['SCRIPT_FILENAME'] = ''; 2066 $env['REQUEST_METHOD'] = ''; 2067 $env['CONTENT_TYPE'] = ''; 2068 $env['CONTENT_LENGTH'] = ''; 2069 $env['TZ'] = ''; 2070 2071 if (!empty($section_text['ENV'])) { 2072 2073 foreach (explode("\n", trim($section_text['ENV'])) as $e) { 2074 $e = explode('=', trim($e), 2); 2075 2076 if (!empty($e[0]) && isset($e[1])) { 2077 $env[$e[0]] = $e[1]; 2078 } 2079 } 2080 } 2081 2082 // Default ini settings 2083 $ini_settings = $workerID ? array('opcache.cache_id' => "worker$workerID") : array(); 2084 2085 // Additional required extensions 2086 if (array_key_exists('EXTENSIONS', $section_text)) { 2087 $ext_params = array(); 2088 settings2array($ini_overwrites, $ext_params); 2089 $ext_params = settings2params($ext_params); 2090 $ext_dir = `$php $pass_options $extra_options $ext_params -d display_errors=0 -r "echo ini_get('extension_dir');"`; 2091 $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); 2092 $loaded = explode(",", `$php $pass_options $extra_options $ext_params -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`); 2093 $ext_prefix = substr(PHP_OS, 0, 3) === "WIN" ? "php_" : ""; 2094 foreach ($extensions as $req_ext) { 2095 if (!in_array($req_ext, $loaded)) { 2096 if ($req_ext == 'opcache') { 2097 $ini_settings['zend_extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; 2098 } else { 2099 $ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; 2100 } 2101 } 2102 } 2103 } 2104 2105 // additional ini overwrites 2106 //$ini_overwrites[] = 'setting=value'; 2107 settings2array($ini_overwrites, $ini_settings); 2108 2109 $orig_ini_settings = settings2params($ini_settings); 2110 2111 // Any special ini settings 2112 // these may overwrite the test defaults... 2113 if (array_key_exists('INI', $section_text)) { 2114 $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); 2115 $section_text['INI'] = str_replace('{TMP}', sys_get_temp_dir(), $section_text['INI']); 2116 settings2array(preg_split("/[\n\r]+/", $section_text['INI']), $ini_settings); 2117 } 2118 2119 $ini_settings = settings2params($ini_settings); 2120 2121 $env['TEST_PHP_EXTRA_ARGS'] = $pass_options . ' ' . $ini_settings; 2122 2123 // Check if test should be skipped. 2124 $info = ''; 2125 $warn = false; 2126 2127 if (array_key_exists('SKIPIF', $section_text)) { 2128 2129 if (trim($section_text['SKIPIF'])) { 2130 show_file_block('skip', $section_text['SKIPIF']); 2131 save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif); 2132 $extra = substr(PHP_OS, 0, 3) !== "WIN" ? 2133 "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; 2134 2135 if ($valgrind) { 2136 $env['USE_ZEND_ALLOC'] = '0'; 2137 $env['ZEND_DONT_UNLOAD_MODULES'] = 1; 2138 } 2139 2140 junit_start_timer($shortname); 2141 2142 $output = system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=0 \"$test_skipif\"", $env); 2143 2144 junit_finish_timer($shortname); 2145 2146 if (!$cfg['keep']['skip']) { 2147 @unlink($test_skipif); 2148 } 2149 2150 if (!strncasecmp('skip', ltrim($output), 4)) { 2151 2152 if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) { 2153 show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); 2154 } else { 2155 show_result('SKIP', $tested, $tested_file, '', $temp_filenames); 2156 } 2157 2158 if (!$cfg['keep']['skip']) { 2159 @unlink($test_skipif); 2160 } 2161 2162 $message = !empty($m[1]) ? $m[1] : ''; 2163 junit_mark_test_as('SKIP', $shortname, $tested, null, $message); 2164 return 'SKIPPED'; 2165 } 2166 2167 if (!strncasecmp('info', ltrim($output), 4)) { 2168 if (preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) { 2169 $info = " (info: $m[1])"; 2170 } 2171 } 2172 2173 if (!strncasecmp('warn', ltrim($output), 4)) { 2174 if (preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) { 2175 $warn = true; /* only if there is a reason */ 2176 $info = " (warn: $m[1])"; 2177 } 2178 } 2179 2180 if (!strncasecmp('xfail', ltrim($output), 5)) { 2181 // Pretend we have an XFAIL section 2182 $section_text['XFAIL'] = trim(substr(ltrim($output), 5)); 2183 } 2184 } 2185 } 2186 2187 if (!extension_loaded("zlib") 2188 && (array_key_exists("GZIP_POST", $section_text) 2189 || array_key_exists("DEFLATE_POST", $section_text))) { 2190 $message = "ext/zlib required"; 2191 show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames); 2192 junit_mark_test_as('SKIP', $shortname, $tested, null, $message); 2193 return 'SKIPPED'; 2194 } 2195 2196 if (isset($section_text['REDIRECTTEST'])) { 2197 $test_files = array(); 2198 2199 $IN_REDIRECT = eval($section_text['REDIRECTTEST']); 2200 $IN_REDIRECT['via'] = "via [$shortname]\n\t"; 2201 $IN_REDIRECT['dir'] = realpath(dirname($file)); 2202 $IN_REDIRECT['prefix'] = trim($section_text['TEST']); 2203 2204 if (!empty($IN_REDIRECT['TESTS'])) { 2205 2206 if (is_array($org_file)) { 2207 $test_files[] = $org_file[1]; 2208 } else { 2209 $GLOBALS['test_files'] = $test_files; 2210 find_files($IN_REDIRECT['TESTS']); 2211 2212 foreach ($GLOBALS['test_files'] as $f) { 2213 $test_files[] = array($f, $file); 2214 } 2215 } 2216 $test_cnt += count($test_files) - 1; 2217 $test_idx--; 2218 2219 show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file); 2220 2221 // set up environment 2222 $redirenv = array_merge($environment, $IN_REDIRECT['ENV']); 2223 $redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR; 2224 2225 usort($test_files, "test_sort"); 2226 run_all_tests($test_files, $redirenv, $tested); 2227 2228 show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file); 2229 2230 // a redirected test never fails 2231 $IN_REDIRECT = false; 2232 2233 junit_mark_test_as('PASS', $shortname, $tested); 2234 return 'REDIR'; 2235 2236 } else { 2237 2238 $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; 2239 show_result("BORK", $bork_info, '', $temp_filenames); 2240 $PHP_FAILED_TESTS['BORKED'][] = array( 2241 'name' => $file, 2242 'test_name' => '', 2243 'output' => '', 2244 'diff' => '', 2245 'info' => "$bork_info [$file]", 2246 ); 2247 } 2248 } 2249 2250 if (is_array($org_file) || isset($section_text['REDIRECTTEST'])) { 2251 2252 if (is_array($org_file)) { 2253 $file = $org_file[0]; 2254 } 2255 2256 $bork_info = "Redirected test did not contain redirection info"; 2257 show_result("BORK", $bork_info, '', $temp_filenames); 2258 $PHP_FAILED_TESTS['BORKED'][] = array( 2259 'name' => $file, 2260 'test_name' => '', 2261 'output' => '', 2262 'diff' => '', 2263 'info' => "$bork_info [$file]", 2264 ); 2265 2266 junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info); 2267 2268 return 'BORKED'; 2269 } 2270 2271 // We've satisfied the preconditions - run the test! 2272 if (isset($section_text['FILE'])) { 2273 show_file_block('php', $section_text['FILE'], 'TEST'); 2274 save_text($test_file, $section_text['FILE'], $temp_file); 2275 } else { 2276 $test_file = $temp_file = ""; 2277 } 2278 2279 if (array_key_exists('GET', $section_text)) { 2280 $query_string = trim($section_text['GET']); 2281 } else { 2282 $query_string = ''; 2283 } 2284 2285 $env['REDIRECT_STATUS'] = '1'; 2286 if (empty($env['QUERY_STRING'])) { 2287 $env['QUERY_STRING'] = $query_string; 2288 } 2289 if (empty($env['PATH_TRANSLATED'])) { 2290 $env['PATH_TRANSLATED'] = $test_file; 2291 } 2292 if (empty($env['SCRIPT_FILENAME'])) { 2293 $env['SCRIPT_FILENAME'] = $test_file; 2294 } 2295 2296 if (array_key_exists('COOKIE', $section_text)) { 2297 $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); 2298 } else { 2299 $env['HTTP_COOKIE'] = ''; 2300 } 2301 2302 $args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : ''; 2303 2304 if ($preload && !empty($test_file)) { 2305 save_text($preload_filename, "<?php opcache_compile_file('$test_file');"); 2306 $local_pass_options = $pass_options; 2307 unset($pass_options); 2308 $pass_options = $local_pass_options; 2309 $pass_options .= " -d opcache.preload=" . $preload_filename; 2310 } 2311 2312 if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) { 2313 2314 $post = trim($section_text['POST_RAW']); 2315 $raw_lines = explode("\n", $post); 2316 2317 $request = ''; 2318 $started = false; 2319 2320 foreach ($raw_lines as $line) { 2321 2322 if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) { 2323 $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); 2324 continue; 2325 } 2326 2327 if ($started) { 2328 $request .= "\n"; 2329 } 2330 2331 $started = true; 2332 $request .= $line; 2333 } 2334 2335 $env['CONTENT_LENGTH'] = strlen($request); 2336 $env['REQUEST_METHOD'] = 'POST'; 2337 2338 if (empty($request)) { 2339 junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request'); 2340 return 'BORKED'; 2341 } 2342 2343 save_text($tmp_post, $request); 2344 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2345 2346 } elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) { 2347 2348 $post = trim($section_text['PUT']); 2349 $raw_lines = explode("\n", $post); 2350 2351 $request = ''; 2352 $started = false; 2353 2354 foreach ($raw_lines as $line) { 2355 2356 if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) { 2357 $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); 2358 continue; 2359 } 2360 2361 if ($started) { 2362 $request .= "\n"; 2363 } 2364 2365 $started = true; 2366 $request .= $line; 2367 } 2368 2369 $env['CONTENT_LENGTH'] = strlen($request); 2370 $env['REQUEST_METHOD'] = 'PUT'; 2371 2372 if (empty($request)) { 2373 junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request'); 2374 return 'BORKED'; 2375 } 2376 2377 save_text($tmp_post, $request); 2378 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2379 2380 } else if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { 2381 2382 $post = trim($section_text['POST']); 2383 $content_length = strlen($post); 2384 save_text($tmp_post, $post); 2385 2386 $env['REQUEST_METHOD'] = 'POST'; 2387 if (empty($env['CONTENT_TYPE'])) { 2388 $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 2389 } 2390 2391 if (empty($env['CONTENT_LENGTH'])) { 2392 $env['CONTENT_LENGTH'] = $content_length; 2393 } 2394 2395 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2396 2397 } else if (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) { 2398 2399 $post = trim($section_text['GZIP_POST']); 2400 $post = gzencode($post, 9, FORCE_GZIP); 2401 $env['HTTP_CONTENT_ENCODING'] = 'gzip'; 2402 2403 save_text($tmp_post, $post); 2404 $content_length = strlen($post); 2405 2406 $env['REQUEST_METHOD'] = 'POST'; 2407 $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 2408 $env['CONTENT_LENGTH'] = $content_length; 2409 2410 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2411 2412 } else if (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) { 2413 $post = trim($section_text['DEFLATE_POST']); 2414 $post = gzcompress($post, 9); 2415 $env['HTTP_CONTENT_ENCODING'] = 'deflate'; 2416 save_text($tmp_post, $post); 2417 $content_length = strlen($post); 2418 2419 $env['REQUEST_METHOD'] = 'POST'; 2420 $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 2421 $env['CONTENT_LENGTH'] = $content_length; 2422 2423 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2424 2425 } else { 2426 2427 $env['REQUEST_METHOD'] = 'GET'; 2428 $env['CONTENT_TYPE'] = ''; 2429 $env['CONTENT_LENGTH'] = ''; 2430 2431 $cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args$cmdRedirect"; 2432 } 2433 2434 if ($valgrind) { 2435 $env['USE_ZEND_ALLOC'] = '0'; 2436 $env['ZEND_DONT_UNLOAD_MODULES'] = 1; 2437 2438 $cmd = $valgrind->wrapCommand($cmd, $memcheck_filename, strpos($test_file, "pcre") !== false); 2439 } 2440 2441 if ($DETAILED) echo " 2442CONTENT_LENGTH = " . $env['CONTENT_LENGTH'] . " 2443CONTENT_TYPE = " . $env['CONTENT_TYPE'] . " 2444PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . " 2445QUERY_STRING = " . $env['QUERY_STRING'] . " 2446REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . " 2447REQUEST_METHOD = " . $env['REQUEST_METHOD'] . " 2448SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . " 2449HTTP_COOKIE = " . $env['HTTP_COOKIE'] . " 2450COMMAND $cmd 2451"; 2452 2453 junit_start_timer($shortname); 2454 $hrtime = hrtime(); 2455 $startTime = $hrtime[0] * 1000000000 + $hrtime[1]; 2456 2457 $out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr); 2458 2459 junit_finish_timer($shortname); 2460 $hrtime = hrtime(); 2461 $time = $hrtime[0] * 1000000000 + $hrtime[1] - $startTime; 2462 if ($time >= $slow_min_ms * 1000000) { 2463 $PHP_FAILED_TESTS['SLOW'][] = array( 2464 'name' => $file, 2465 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 2466 'output' => '', 2467 'diff' => '', 2468 'info' => $time / 1000000000, 2469 ); 2470 } 2471 2472 if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) { 2473 2474 if (trim($section_text['CLEAN'])) { 2475 show_file_block('clean', $section_text['CLEAN']); 2476 save_text($test_clean, trim($section_text['CLEAN']), $temp_clean); 2477 2478 if (!$no_clean) { 2479 $extra = substr(PHP_OS, 0, 3) !== "WIN" ? 2480 "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; 2481 system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env); 2482 } 2483 2484 if (!$cfg['keep']['clean']) { 2485 @unlink($test_clean); 2486 } 2487 } 2488 } 2489 2490 @unlink($preload_filename); 2491 2492 $leaked = false; 2493 $passed = false; 2494 2495 if ($valgrind) { // leak check 2496 $leaked = filesize($memcheck_filename) > 0; 2497 2498 if (!$leaked) { 2499 @unlink($memcheck_filename); 2500 } 2501 } 2502 2503 // Does the output match what is expected? 2504 $output = preg_replace("/\r\n/", "\n", trim($out)); 2505 2506 /* when using CGI, strip the headers from the output */ 2507 $headers = array(); 2508 2509 if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { 2510 $output = trim($match[2]); 2511 $rh = preg_split("/[\n\r]+/", $match[1]); 2512 2513 foreach ($rh as $line) { 2514 if (strpos($line, ':') !== false) { 2515 $line = explode(':', $line, 2); 2516 $headers[trim($line[0])] = trim($line[1]); 2517 } 2518 } 2519 } 2520 2521 $failed_headers = false; 2522 2523 if (isset($section_text['EXPECTHEADERS'])) { 2524 $want = array(); 2525 $wanted_headers = array(); 2526 $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); 2527 2528 foreach ($lines as $line) { 2529 if (strpos($line, ':') !== false) { 2530 $line = explode(':', $line, 2); 2531 $want[trim($line[0])] = trim($line[1]); 2532 $wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]); 2533 } 2534 } 2535 2536 $output_headers = array(); 2537 2538 foreach ($want as $k => $v) { 2539 2540 if (isset($headers[$k])) { 2541 $output_headers[] = $k . ': ' . $headers[$k]; 2542 } 2543 2544 if (!isset($headers[$k]) || $headers[$k] != $v) { 2545 $failed_headers = true; 2546 } 2547 } 2548 2549 ksort($wanted_headers); 2550 $wanted_headers = implode("\n", $wanted_headers); 2551 ksort($output_headers); 2552 $output_headers = implode("\n", $output_headers); 2553 } 2554 2555 show_file_block('out', $output); 2556 2557 if ($preload) { 2558 $output = trim(preg_replace("/\n?Warning: Can't preload [^\n]*\n?/", "", $output)); 2559 } 2560 2561 if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { 2562 2563 if (isset($section_text['EXPECTF'])) { 2564 $wanted = trim($section_text['EXPECTF']); 2565 } else { 2566 $wanted = trim($section_text['EXPECTREGEX']); 2567 } 2568 2569 show_file_block('exp', $wanted); 2570 $wanted_re = preg_replace('/\r\n/', "\n", $wanted); 2571 2572 if (isset($section_text['EXPECTF'])) { 2573 2574 // do preg_quote, but miss out any %r delimited sections 2575 $temp = ""; 2576 $r = "%r"; 2577 $startOffset = 0; 2578 $length = strlen($wanted_re); 2579 while ($startOffset < $length) { 2580 $start = strpos($wanted_re, $r, $startOffset); 2581 if ($start !== false) { 2582 // we have found a start tag 2583 $end = strpos($wanted_re, $r, $start + 2); 2584 if ($end === false) { 2585 // unbalanced tag, ignore it. 2586 $end = $start = $length; 2587 } 2588 } else { 2589 // no more %r sections 2590 $start = $end = $length; 2591 } 2592 // quote a non re portion of the string 2593 $temp .= preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/'); 2594 // add the re unquoted. 2595 if ($end > $start) { 2596 $temp .= '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')'; 2597 } 2598 $startOffset = $end + 2; 2599 } 2600 $wanted_re = $temp; 2601 2602 // Stick to basics 2603 $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re); 2604 $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re); 2605 $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re); 2606 $wanted_re = str_replace('%a', '.+', $wanted_re); 2607 $wanted_re = str_replace('%A', '.*', $wanted_re); 2608 $wanted_re = str_replace('%w', '\s*', $wanted_re); 2609 $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re); 2610 $wanted_re = str_replace('%d', '\d+', $wanted_re); 2611 $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re); 2612 $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re); 2613 $wanted_re = str_replace('%c', '.', $wanted_re); 2614 // %f allows two points "-.0.0" but that is the best *simple* expression 2615 } 2616/* DEBUG YOUR REGEX HERE 2617 var_dump($wanted_re); 2618 print(str_repeat('=', 80) . "\n"); 2619 var_dump($output); 2620 */ 2621 if (preg_match("/^$wanted_re\$/s", $output)) { 2622 $passed = true; 2623 if (!$cfg['keep']['php']) { 2624 @unlink($test_file); 2625 } 2626 @unlink($tmp_post); 2627 2628 if (!$leaked && !$failed_headers) { 2629 if (isset($section_text['XFAIL'])) { 2630 $warn = true; 2631 $info = " (warn: XFAIL section but test passes)"; 2632 } else if (isset($section_text['XLEAK'])) { 2633 $warn = true; 2634 $info = " (warn: XLEAK section but test passes)"; 2635 } else { 2636 show_result("PASS", $tested, $tested_file, '', $temp_filenames); 2637 junit_mark_test_as('PASS', $shortname, $tested); 2638 return 'PASSED'; 2639 } 2640 } 2641 } 2642 2643 } else { 2644 2645 $wanted = trim($section_text['EXPECT']); 2646 $wanted = preg_replace('/\r\n/', "\n", $wanted); 2647 show_file_block('exp', $wanted); 2648 2649 // compare and leave on success 2650 if (!strcmp($output, $wanted)) { 2651 $passed = true; 2652 2653 if (!$cfg['keep']['php']) { 2654 @unlink($test_file); 2655 } 2656 @unlink($tmp_post); 2657 2658 if (!$leaked && !$failed_headers) { 2659 if (isset($section_text['XFAIL'])) { 2660 $warn = true; 2661 $info = " (warn: XFAIL section but test passes)"; 2662 } elseif (isset($section_text['XLEAK'])) { 2663 $warn = true; 2664 $info = " (warn: XLEAK section but test passes)"; 2665 } else { 2666 show_result("PASS", $tested, $tested_file, '', $temp_filenames); 2667 junit_mark_test_as('PASS', $shortname, $tested); 2668 return 'PASSED'; 2669 } 2670 } 2671 } 2672 2673 $wanted_re = null; 2674 } 2675 2676 // Test failed so we need to report details. 2677 if ($failed_headers) { 2678 $passed = false; 2679 $wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted; 2680 $output = $output_headers . "\n--HEADERS--\n" . $output; 2681 2682 if (isset($wanted_re)) { 2683 $wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re; 2684 } 2685 } 2686 2687 if ($leaked) { 2688 $restype[] = isset($section_text['XLEAK']) ? 2689 'XLEAK' : 'LEAK'; 2690 } 2691 2692 if ($warn) { 2693 $restype[] = 'WARN'; 2694 } 2695 2696 if (!$passed) { 2697 if (isset($section_text['XFAIL'])) { 2698 $restype[] = 'XFAIL'; 2699 $info = ' XFAIL REASON: ' . rtrim($section_text['XFAIL']); 2700 } else if (isset($section_text['XLEAK'])) { 2701 $restype[] = 'XLEAK'; 2702 $info = ' XLEAK REASON: ' . rtrim($section_text['XLEAK']); 2703 } else { 2704 $restype[] = 'FAIL'; 2705 } 2706 } 2707 2708 if (!$passed) { 2709 2710 // write .exp 2711 if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted, FILE_BINARY) === false) { 2712 error("Cannot create expected test output - $exp_filename"); 2713 } 2714 2715 // write .out 2716 if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output, FILE_BINARY) === false) { 2717 error("Cannot create test output - $output_filename"); 2718 } 2719 2720 // write .diff 2721 $diff = generate_diff($wanted, $wanted_re, $output); 2722 if (is_array($IN_REDIRECT)) { 2723 $orig_shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); 2724 $diff = "# original source file: $orig_shortname\n" . $diff; 2725 } 2726 show_file_block('diff', $diff); 2727 if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff, FILE_BINARY) === false) { 2728 error("Cannot create test diff - $diff_filename"); 2729 } 2730 2731 // write .sh 2732 if (strpos($log_format, 'S') !== false && file_put_contents($sh_filename, "#!/bin/sh 2733 2734{$cmd} 2735", FILE_BINARY) === false) { 2736 error("Cannot create test shell script - $sh_filename"); 2737 } 2738 chmod($sh_filename, 0755); 2739 2740 // write .log 2741 if (strpos($log_format, 'L') !== false && file_put_contents($log_filename, " 2742---- EXPECTED OUTPUT 2743$wanted 2744---- ACTUAL OUTPUT 2745$output 2746---- FAILED 2747", FILE_BINARY) === false) { 2748 error("Cannot create test log - $log_filename"); 2749 error_report($file, $log_filename, $tested); 2750 } 2751 } 2752 2753 if ($valgrind && $leaked && $cfg["show"]["mem"]) { 2754 show_file_block('mem', file_get_contents($memcheck_filename)); 2755 } 2756 2757 show_result(implode('&', $restype), $tested, $tested_file, $info, $temp_filenames); 2758 2759 foreach ($restype as $type) { 2760 $PHP_FAILED_TESTS[$type . 'ED'][] = array( 2761 'name' => $file, 2762 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 2763 'output' => $output_filename, 2764 'diff' => $diff_filename, 2765 'info' => $info, 2766 ); 2767 } 2768 2769 $diff = empty($diff) ? '' : preg_replace('/\e/', '<esc>', $diff); 2770 2771 junit_mark_test_as($restype, $shortname, $tested, null, $info, $diff); 2772 2773 return $restype[0] . 'ED'; 2774} 2775 2776function comp_line($l1, $l2, $is_reg) 2777{ 2778 if ($is_reg) { 2779 return preg_match('/^' . $l1 . '$/s', $l2); 2780 } else { 2781 return !strcmp($l1, $l2); 2782 } 2783} 2784 2785function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2, $steps) 2786{ 2787 $equal = 0; 2788 2789 while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { 2790 $idx1++; 2791 $idx2++; 2792 $equal++; 2793 $steps--; 2794 } 2795 if (--$steps > 0) { 2796 $eq1 = 0; 2797 $st = $steps / 2; 2798 2799 for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) { 2800 $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st); 2801 2802 if ($eq > $eq1) { 2803 $eq1 = $eq; 2804 } 2805 } 2806 2807 $eq2 = 0; 2808 $st = $steps; 2809 2810 for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) { 2811 $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st); 2812 if ($eq > $eq2) { 2813 $eq2 = $eq; 2814 } 2815 } 2816 2817 if ($eq1 > $eq2) { 2818 $equal += $eq1; 2819 } else if ($eq2 > 0) { 2820 $equal += $eq2; 2821 } 2822 } 2823 2824 return $equal; 2825} 2826 2827function generate_array_diff($ar1, $ar2, $is_reg, $w) 2828{ 2829 $idx1 = 0; 2830 $cnt1 = @count($ar1); 2831 $idx2 = 0; 2832 $cnt2 = @count($ar2); 2833 $diff = array(); 2834 $old1 = array(); 2835 $old2 = array(); 2836 2837 while ($idx1 < $cnt1 && $idx2 < $cnt2) { 2838 2839 if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { 2840 $idx1++; 2841 $idx2++; 2842 continue; 2843 } else { 2844 2845 $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1 + 1, $idx2, $cnt1, $cnt2, 10); 2846 $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2 + 1, $cnt1, $cnt2, 10); 2847 2848 if ($c1 > $c2) { 2849 $old1[$idx1] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; 2850 } else if ($c2 > 0) { 2851 $old2[$idx2] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; 2852 } else { 2853 $old1[$idx1] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; 2854 $old2[$idx2] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; 2855 } 2856 } 2857 } 2858 2859 reset($old1); 2860 $k1 = key($old1); 2861 $l1 = -2; 2862 reset($old2); 2863 $k2 = key($old2); 2864 $l2 = -2; 2865 2866 while ($k1 !== null || $k2 !== null) { 2867 2868 if ($k1 == $l1 + 1 || $k2 === null) { 2869 $l1 = $k1; 2870 $diff[] = current($old1); 2871 $k1 = next($old1) ? key($old1) : null; 2872 } else if ($k2 == $l2 + 1 || $k1 === null) { 2873 $l2 = $k2; 2874 $diff[] = current($old2); 2875 $k2 = next($old2) ? key($old2) : null; 2876 } else if ($k1 < $k2) { 2877 $l1 = $k1; 2878 $diff[] = current($old1); 2879 $k1 = next($old1) ? key($old1) : null; 2880 } else { 2881 $l2 = $k2; 2882 $diff[] = current($old2); 2883 $k2 = next($old2) ? key($old2) : null; 2884 } 2885 } 2886 2887 while ($idx1 < $cnt1) { 2888 $diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; 2889 } 2890 2891 while ($idx2 < $cnt2) { 2892 $diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; 2893 } 2894 2895 return $diff; 2896} 2897 2898function generate_diff($wanted, $wanted_re, $output) 2899{ 2900 $w = explode("\n", $wanted); 2901 $o = explode("\n", $output); 2902 $r = is_null($wanted_re) ? $w : explode("\n", $wanted_re); 2903 $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w); 2904 2905 return implode(PHP_EOL, $diff); 2906} 2907 2908function error($message) 2909{ 2910 echo "ERROR: {$message}\n"; 2911 exit(1); 2912} 2913 2914function settings2array($settings, &$ini_settings) 2915{ 2916 foreach ($settings as $setting) { 2917 2918 if (strpos($setting, '=') !== false) { 2919 $setting = explode("=", $setting, 2); 2920 $name = trim($setting[0]); 2921 $value = trim($setting[1]); 2922 2923 if ($name == 'extension' || $name == 'zend_extension') { 2924 2925 if (!isset($ini_settings[$name])) { 2926 $ini_settings[$name] = array(); 2927 } 2928 2929 $ini_settings[$name][] = $value; 2930 2931 } else { 2932 $ini_settings[$name] = $value; 2933 } 2934 } 2935 } 2936} 2937 2938function settings2params($ini_settings) 2939{ 2940 $settings = ''; 2941 2942 foreach($ini_settings as $name => $value) { 2943 2944 if (is_array($value)) { 2945 foreach($value as $val) { 2946 $val = addslashes($val); 2947 $settings .= " -d \"$name=$val\""; 2948 } 2949 } else { 2950 if (substr(PHP_OS, 0, 3) == "WIN" && !empty($value) && $value[0] == '"') { 2951 $len = strlen($value); 2952 2953 if ($value[$len - 1] == '"') { 2954 $value[0] = "'"; 2955 $value[$len - 1] = "'"; 2956 } 2957 } else { 2958 $value = addslashes($value); 2959 } 2960 2961 $settings .= " -d \"$name=$value\""; 2962 } 2963 } 2964 2965 return $settings; 2966} 2967 2968function compute_summary() 2969{ 2970 global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results; 2971 2972 $n_total = count($test_results); 2973 $n_total += $ignored_by_ext; 2974 $sum_results = array( 2975 'PASSED' => 0, 2976 'WARNED' => 0, 2977 'SKIPPED' => 0, 2978 'FAILED' => 0, 2979 'BORKED' => 0, 2980 'LEAKED' => 0, 2981 'XFAILED' => 0, 2982 'XLEAKED' => 0 2983 ); 2984 2985 foreach ($test_results as $v) { 2986 $sum_results[$v]++; 2987 } 2988 2989 $sum_results['SKIPPED'] += $ignored_by_ext; 2990 $percent_results = array(); 2991 2992 foreach ($sum_results as $v => $n) { 2993 $percent_results[$v] = (100.0 * $n) / $n_total; 2994 } 2995} 2996 2997function get_summary($show_ext_summary, $show_html) 2998{ 2999 global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $valgrind; 3000 3001 $x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED']; 3002 3003 if ($x_total) { 3004 $x_warned = (100.0 * $sum_results['WARNED']) / $x_total; 3005 $x_failed = (100.0 * $sum_results['FAILED']) / $x_total; 3006 $x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total; 3007 $x_xleaked = (100.0 * $sum_results['XLEAKED']) / $x_total; 3008 $x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total; 3009 $x_passed = (100.0 * $sum_results['PASSED']) / $x_total; 3010 } else { 3011 $x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = $x_xleaked = 0; 3012 } 3013 3014 $summary = ''; 3015 3016 if ($show_html) { 3017 $summary .= "<pre>\n"; 3018 } 3019 3020 if ($show_ext_summary) { 3021 $summary .= ' 3022===================================================================== 3023TEST RESULT SUMMARY 3024--------------------------------------------------------------------- 3025Exts skipped : ' . sprintf('%4d', $exts_skipped) . ' 3026Exts tested : ' . sprintf('%4d', $exts_tested) . ' 3027--------------------------------------------------------------------- 3028'; 3029 } 3030 3031 $summary .= ' 3032Number of tests : ' . sprintf('%4d', $n_total) . ' ' . sprintf('%8d', $x_total); 3033 3034 if ($sum_results['BORKED']) { 3035 $summary .= ' 3036Tests borked : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------'; 3037 } 3038 3039 $summary .= ' 3040Tests skipped : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' -------- 3041Tests warned : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . ' 3042Tests failed : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed); 3043 3044 if ($sum_results['XFAILED']) { 3045 $summary .= ' 3046Expected fail : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed); 3047 } 3048 3049 if ($valgrind) { 3050 $summary .= ' 3051Tests leaked : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked); 3052 if ($sum_results['XLEAKED']) { 3053 $summary .= ' 3054Expected leak : ' . sprintf('%4d (%5.1f%%)', $sum_results['XLEAKED'], $percent_results['XLEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_xleaked); 3055 } 3056 } 3057 3058 $summary .= ' 3059Tests passed : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . ' 3060--------------------------------------------------------------------- 3061Time taken : ' . sprintf('%4d seconds', $end_time - $start_time) . ' 3062===================================================================== 3063'; 3064 $failed_test_summary = ''; 3065 3066 if (count($PHP_FAILED_TESTS['SLOW'])) { 3067 usort($PHP_FAILED_TESTS['SLOW'], function ($a, $b) { 3068 return $a['info'] < $b['info'] ? 1 : -1; 3069 }); 3070 3071 $failed_test_summary .= ' 3072===================================================================== 3073SLOW TEST SUMMARY 3074--------------------------------------------------------------------- 3075'; 3076 foreach ($PHP_FAILED_TESTS['SLOW'] as $failed_test_data) { 3077 $failed_test_summary .= sprintf('(%.3f s) ', $failed_test_data['info']) . $failed_test_data['test_name'] . "\n"; 3078 } 3079 $failed_test_summary .= "=====================================================================\n"; 3080 } 3081 3082 if (count($PHP_FAILED_TESTS['XFAILED'])) { 3083 $failed_test_summary .= ' 3084===================================================================== 3085EXPECTED FAILED TEST SUMMARY 3086--------------------------------------------------------------------- 3087'; 3088 foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) { 3089 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3090 } 3091 $failed_test_summary .= "=====================================================================\n"; 3092 } 3093 3094 if (count($PHP_FAILED_TESTS['BORKED'])) { 3095 $failed_test_summary .= ' 3096===================================================================== 3097BORKED TEST SUMMARY 3098--------------------------------------------------------------------- 3099'; 3100 foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) { 3101 $failed_test_summary .= $failed_test_data['info'] . "\n"; 3102 } 3103 3104 $failed_test_summary .= "=====================================================================\n"; 3105 } 3106 3107 if (count($PHP_FAILED_TESTS['FAILED'])) { 3108 $failed_test_summary .= ' 3109===================================================================== 3110FAILED TEST SUMMARY 3111--------------------------------------------------------------------- 3112'; 3113 foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) { 3114 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3115 } 3116 $failed_test_summary .= "=====================================================================\n"; 3117 } 3118 if (count($PHP_FAILED_TESTS['WARNED'])) { 3119 $failed_test_summary .= ' 3120===================================================================== 3121WARNED TEST SUMMARY 3122--------------------------------------------------------------------- 3123'; 3124 foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) { 3125 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3126 } 3127 3128 $failed_test_summary .= "=====================================================================\n"; 3129 } 3130 3131 if (count($PHP_FAILED_TESTS['LEAKED'])) { 3132 $failed_test_summary .= ' 3133===================================================================== 3134LEAKED TEST SUMMARY 3135--------------------------------------------------------------------- 3136'; 3137 foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) { 3138 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3139 } 3140 3141 $failed_test_summary .= "=====================================================================\n"; 3142 } 3143 3144 if (count($PHP_FAILED_TESTS['XLEAKED'])) { 3145 $failed_test_summary .= ' 3146===================================================================== 3147EXPECTED LEAK TEST SUMMARY 3148--------------------------------------------------------------------- 3149'; 3150 foreach ($PHP_FAILED_TESTS['XLEAKED'] as $failed_test_data) { 3151 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3152 } 3153 3154 $failed_test_summary .= "=====================================================================\n"; 3155 } 3156 3157 if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) { 3158 $summary .= $failed_test_summary; 3159 } 3160 3161 if ($show_html) { 3162 $summary .= "</pre>"; 3163 } 3164 3165 return $summary; 3166} 3167 3168function show_start($start_time) 3169{ 3170 global $html_output, $html_file; 3171 3172 if ($html_output) { 3173 fwrite($html_file, "<h2>Time Start: " . date('Y-m-d H:i:s', $start_time) . "</h2>\n"); 3174 fwrite($html_file, "<table>\n"); 3175 } 3176 3177 echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n"; 3178} 3179 3180function show_end($end_time) 3181{ 3182 global $html_output, $html_file; 3183 3184 if ($html_output) { 3185 fwrite($html_file, "</table>\n"); 3186 fwrite($html_file, "<h2>Time End: " . date('Y-m-d H:i:s', $end_time) . "</h2>\n"); 3187 } 3188 3189 echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n"; 3190} 3191 3192function show_summary() 3193{ 3194 global $html_output, $html_file; 3195 3196 if ($html_output) { 3197 fwrite($html_file, "<hr/>\n" . get_summary(true, true)); 3198 } 3199 3200 echo get_summary(true, false); 3201} 3202 3203function show_redirect_start($tests, $tested, $tested_file) 3204{ 3205 global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; 3206 3207 if ($html_output) { 3208 fwrite($html_file, "<tr><td colspan='3'>---> $tests ($tested [$tested_file]) begin</td></tr>\n"); 3209 } 3210 3211 if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { 3212 echo "REDIRECT $tests ($tested [$tested_file]) begin\n"; 3213 } else { 3214 clear_show_test(); 3215 } 3216} 3217 3218function show_redirect_ends($tests, $tested, $tested_file) 3219{ 3220 global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; 3221 3222 if ($html_output) { 3223 fwrite($html_file, "<tr><td colspan='3'>---> $tests ($tested [$tested_file]) done</td></tr>\n"); 3224 } 3225 3226 if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { 3227 echo "REDIRECT $tests ($tested [$tested_file]) done\n"; 3228 } else { 3229 clear_show_test(); 3230 } 3231} 3232 3233function show_test($test_idx, $shortname) 3234{ 3235 global $test_cnt; 3236 global $line_length; 3237 3238 $str = "TEST $test_idx/$test_cnt [$shortname]\r"; 3239 $line_length = strlen($str); 3240 echo $str; 3241 flush(); 3242} 3243 3244function clear_show_test() { 3245 global $line_length; 3246 // Parallel testing 3247 global $workerID; 3248 3249 if (!$workerID) { 3250 // Write over the last line to avoid random trailing chars on next echo 3251 echo str_repeat(" ", $line_length), "\r"; 3252 } 3253} 3254 3255function parse_conflicts(string $text) : array { 3256 // Strip comments 3257 $text = preg_replace('/#.*/', '', $text); 3258 return array_map('trim', explode("\n", trim($text))); 3259} 3260 3261function show_result($result, $tested, $tested_file, $extra = '', $temp_filenames = null) 3262{ 3263 global $html_output, $html_file, $temp_target, $temp_urlbase, $line_length, $SHOW_ONLY_GROUPS; 3264 3265 if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) { 3266 echo "$result $tested [$tested_file] $extra\n"; 3267 } else if (!$SHOW_ONLY_GROUPS) { 3268 clear_show_test(); 3269 } 3270 3271 if ($html_output) { 3272 3273 if (isset($temp_filenames['file']) && file_exists($temp_filenames['file'])) { 3274 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['file']); 3275 $tested = "<a href='$url'>$tested</a>"; 3276 } 3277 3278 if (isset($temp_filenames['skip']) && file_exists($temp_filenames['skip'])) { 3279 3280 if (empty($extra)) { 3281 $extra = "skipif"; 3282 } 3283 3284 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['skip']); 3285 $extra = "<a href='$url'>$extra</a>"; 3286 3287 } else if (empty($extra)) { 3288 $extra = " "; 3289 } 3290 3291 if (isset($temp_filenames['diff']) && file_exists($temp_filenames['diff'])) { 3292 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['diff']); 3293 $diff = "<a href='$url'>diff</a>"; 3294 } else { 3295 $diff = " "; 3296 } 3297 3298 if (isset($temp_filenames['mem']) && file_exists($temp_filenames['mem'])) { 3299 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['mem']); 3300 $mem = "<a href='$url'>leaks</a>"; 3301 } else { 3302 $mem = " "; 3303 } 3304 3305 fwrite( 3306 $html_file, 3307 "<tr>" . 3308 "<td>$result</td>" . 3309 "<td>$tested</td>" . 3310 "<td>$extra</td>" . 3311 "<td>$diff</td>" . 3312 "<td>$mem</td>" . 3313 "</tr>\n" 3314 ); 3315 } 3316} 3317 3318function junit_init() 3319{ 3320 // Check whether a junit log is wanted. 3321 global $workerID; 3322 $JUNIT = getenv('TEST_PHP_JUNIT'); 3323 if (empty($JUNIT)) { 3324 $GLOBALS['JUNIT'] = false; 3325 return; 3326 } 3327 if ($workerID) { 3328 $fp = null; 3329 } else if (!$fp = fopen($JUNIT, 'w')) { 3330 error("Failed to open $JUNIT for writing."); 3331 } 3332 $GLOBALS['JUNIT'] = array( 3333 'fp' => $fp, 3334 'name' => 'PHP', 3335 'test_total' => 0, 3336 'test_pass' => 0, 3337 'test_fail' => 0, 3338 'test_error' => 0, 3339 'test_skip' => 0, 3340 'test_warn' => 0, 3341 'execution_time' => 0, 3342 'suites' => array(), 3343 'files' => array() 3344 ); 3345} 3346 3347function junit_save_xml() 3348{ 3349 global $JUNIT; 3350 if (!junit_enabled()) return; 3351 3352 $xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>' . PHP_EOL; 3353 $xml .= sprintf( 3354 '<testsuites name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL, 3355 $JUNIT['name'], 3356 $JUNIT['test_total'], 3357 $JUNIT['test_fail'], 3358 $JUNIT['test_error'], 3359 $JUNIT['test_skip'], 3360 $JUNIT['execution_time'] 3361 ); 3362 $xml .= junit_get_suite_xml(); 3363 $xml .= '</testsuites>'; 3364 fwrite($JUNIT['fp'], $xml); 3365} 3366 3367function junit_get_suite_xml($suite_name = '') 3368{ 3369 global $JUNIT; 3370 3371 $result = ""; 3372 3373 foreach ($JUNIT['suites'] as $suite_name => $suite) { 3374 $result .= sprintf( 3375 '<testsuite name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL, 3376 $suite['name'], 3377 $suite['test_total'], 3378 $suite['test_fail'], 3379 $suite['test_error'], 3380 $suite['test_skip'], 3381 $suite['execution_time'] 3382 ); 3383 3384 if (!empty($suite_name)) { 3385 foreach ($suite['files'] as $file) { 3386 $result .= $JUNIT['files'][$file]['xml']; 3387 } 3388 } 3389 3390 $result .= '</testsuite>' . PHP_EOL; 3391 } 3392 3393 return $result; 3394} 3395 3396function junit_enabled() 3397{ 3398 global $JUNIT; 3399 return !empty($JUNIT); 3400} 3401 3402/** 3403 * @param array|string $type 3404 * @param string $file_name 3405 * @param string $test_name 3406 * @param int|string $time 3407 * @param string $message 3408 * @param string $details 3409 * @return void 3410 */ 3411function junit_mark_test_as($type, $file_name, $test_name, $time = null, $message = '', $details = '') 3412{ 3413 global $JUNIT; 3414 if (!junit_enabled()) return; 3415 3416 $suite = junit_get_suitename_for($file_name); 3417 3418 junit_suite_record($suite, 'test_total'); 3419 3420 $time = $time ?? junit_get_timer($file_name); 3421 junit_suite_record($suite, 'execution_time', $time); 3422 3423 $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8'); 3424 $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) { 3425 return sprintf('[[0x%02x]]', ord($c[0])); 3426 }, $escaped_details); 3427 $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); 3428 3429 $escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES); 3430 $JUNIT['files'][$file_name]['xml'] = "<testcase name='$escaped_test_name' time='$time'>\n"; 3431 3432 if (is_array($type)) { 3433 $output_type = $type[0] . 'ED'; 3434 $temp = array_intersect(array('XFAIL', 'XLEAK', 'FAIL', 'WARN'), $type); 3435 $type = reset($temp); 3436 } else { 3437 $output_type = $type . 'ED'; 3438 } 3439 3440 if ('PASS' == $type || 'XFAIL' == $type || 'XLEAK' == $type) { 3441 junit_suite_record($suite, 'test_pass'); 3442 } elseif ('BORK' == $type) { 3443 junit_suite_record($suite, 'test_error'); 3444 $JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'/>\n"; 3445 } elseif ('SKIP' == $type) { 3446 junit_suite_record($suite, 'test_skip'); 3447 $JUNIT['files'][$file_name]['xml'] .= "<skipped>$escaped_message</skipped>\n"; 3448 } elseif ('WARN' == $type) { 3449 junit_suite_record($suite, 'test_warn'); 3450 $JUNIT['files'][$file_name]['xml'] .= "<warning>$escaped_message</warning>\n"; 3451 } elseif ('FAIL' == $type) { 3452 junit_suite_record($suite, 'test_fail'); 3453 $JUNIT['files'][$file_name]['xml'] .= "<failure type='$output_type' message='$escaped_message'>$escaped_details</failure>\n"; 3454 } else { 3455 junit_suite_record($suite, 'test_error'); 3456 $JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'>$escaped_details</error>\n"; 3457 } 3458 3459 $JUNIT['files'][$file_name]['xml'] .= "</testcase>\n"; 3460 3461} 3462 3463function junit_suite_record($suite, $param, $value = 1) 3464{ 3465 global $JUNIT; 3466 3467 $JUNIT[$param] += $value; 3468 $JUNIT['suites'][$suite][$param] += $value; 3469} 3470 3471function junit_get_timer($file_name) 3472{ 3473 global $JUNIT; 3474 if (!junit_enabled()) return 0; 3475 3476 if (isset($JUNIT['files'][$file_name]['total'])) { 3477 return number_format($JUNIT['files'][$file_name]['total'], 4); 3478 } 3479 3480 return 0; 3481} 3482 3483function junit_start_timer($file_name) 3484{ 3485 global $JUNIT; 3486 if (!junit_enabled()) return; 3487 3488 if (!isset($JUNIT['files'][$file_name]['start'])) { 3489 $JUNIT['files'][$file_name]['start'] = microtime(true); 3490 3491 $suite = junit_get_suitename_for($file_name); 3492 junit_init_suite($suite); 3493 $JUNIT['suites'][$suite]['files'][$file_name] = $file_name; 3494 } 3495} 3496 3497function junit_get_suitename_for($file_name) 3498{ 3499 return junit_path_to_classname(dirname($file_name)); 3500} 3501 3502function junit_path_to_classname($file_name) 3503{ 3504 global $JUNIT; 3505 3506 if (!junit_enabled()) return ''; 3507 3508 $ret = $JUNIT['name']; 3509 $_tmp = array(); 3510 3511 // lookup whether we're in the PHP source checkout 3512 $max = 5; 3513 if (is_file($file_name)) { 3514 $dir = dirname(realpath($file_name)); 3515 } else { 3516 $dir = realpath($file_name); 3517 } 3518 do { 3519 array_unshift($_tmp, basename($dir)); 3520 $chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h"; 3521 $dir = dirname($dir); 3522 } while (!file_exists($chk) && --$max > 0); 3523 if (file_exists($chk)) { 3524 if ($max) { 3525 array_shift($_tmp); 3526 } 3527 foreach ($_tmp as $p) { 3528 $ret .= "." . preg_replace(",[^a-z0-9]+,i", ".", $p); 3529 } 3530 return $ret; 3531 } 3532 3533 return $JUNIT['name'] . '.' . str_replace(array(DIRECTORY_SEPARATOR, '-'), '.', $file_name); 3534} 3535 3536function junit_init_suite($suite_name) 3537{ 3538 global $JUNIT; 3539 if (!junit_enabled()) return; 3540 3541 if (!empty($JUNIT['suites'][$suite_name])) { 3542 return; 3543 } 3544 3545 $JUNIT['suites'][$suite_name] = array( 3546 'name' => $suite_name, 3547 'test_total' => 0, 3548 'test_pass' => 0, 3549 'test_fail' => 0, 3550 'test_error' => 0, 3551 'test_skip' => 0, 3552 'test_warn' => 0, 3553 'files' => array(), 3554 'execution_time' => 0, 3555 ); 3556} 3557 3558function junit_finish_timer($file_name) 3559{ 3560 global $JUNIT; 3561 if (!junit_enabled()) return; 3562 3563 if (!isset($JUNIT['files'][$file_name]['start'])) { 3564 error("Timer for $file_name was not started!"); 3565 } 3566 3567 if (!isset($JUNIT['files'][$file_name]['total'])) { 3568 $JUNIT['files'][$file_name]['total'] = 0; 3569 } 3570 3571 $start = $JUNIT['files'][$file_name]['start']; 3572 $JUNIT['files'][$file_name]['total'] += microtime(true) - $start; 3573 unset($JUNIT['files'][$file_name]['start']); 3574} 3575 3576function junit_merge_results($junit) 3577{ 3578 global $JUNIT; 3579 $JUNIT['test_total'] += $junit['test_total']; 3580 $JUNIT['test_pass'] += $junit['test_pass']; 3581 $JUNIT['test_fail'] += $junit['test_fail']; 3582 $JUNIT['test_error'] += $junit['test_error']; 3583 $JUNIT['test_skip'] += $junit['test_skip']; 3584 $JUNIT['test_warn'] += $junit['test_warn']; 3585 $JUNIT['execution_time'] += $junit['execution_time']; 3586 $JUNIT['files'] += $junit['files']; 3587 foreach ($junit['suites'] as $name => $suite) { 3588 if (!isset($JUNIT['suites'][$name])) { 3589 $JUNIT['suites'][$name] = $suite; 3590 continue; 3591 } 3592 3593 $SUITE =& $JUNIT['suites'][$name]; 3594 $SUITE['test_total'] += $suite['test_total']; 3595 $SUITE['test_pass'] += $suite['test_pass']; 3596 $SUITE['test_fail'] += $suite['test_fail']; 3597 $SUITE['test_error'] += $suite['test_error']; 3598 $SUITE['test_skip'] += $suite['test_skip']; 3599 $SUITE['test_warn'] += $suite['test_warn']; 3600 $SUITE['execution_time'] += $suite['execution_time']; 3601 $SUITE['files'] += $suite['files']; 3602 } 3603} 3604 3605class RuntestsValgrind 3606{ 3607 protected $version = ''; 3608 protected $header = ''; 3609 protected $version_3_3_0 = false; 3610 protected $version_3_8_0 = false; 3611 protected $tool = null; 3612 3613 public function getVersion() 3614 { 3615 return $this->version; 3616 } 3617 3618 public function getHeader() 3619 { 3620 return $this->header; 3621 } 3622 3623 public function __construct(array $environment, string $tool = 'memcheck') 3624 { 3625 $this->tool = $tool; 3626 $header = system_with_timeout("valgrind --tool={$this->tool} --version", $environment); 3627 if (!$header) { 3628 error("Valgrind returned no version info for {$this->tool}, cannot proceed.\n". 3629 "Please check if Valgrind is installed and the tool is named correctly."); 3630 } 3631 $count = 0; 3632 $version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $header, 1, $count); 3633 if ($count != 1) { 3634 error("Valgrind returned invalid version info (\"{$header}\") for {$this->tool}, cannot proceed."); 3635 } 3636 $this->version = $version; 3637 $this->header = sprintf( 3638 "%s (%s)", trim($header), $this->tool); 3639 $this->version_3_3_0 = version_compare($version, '3.3.0', '>='); 3640 $this->version_3_8_0 = version_compare($version, '3.8.0', '>='); 3641 } 3642 3643 public function wrapCommand($cmd, $memcheck_filename, $check_all) 3644 { 3645 $vcmd = "valgrind -q --tool={$this->tool} --trace-children=yes"; 3646 if ($check_all) { 3647 $vcmd .= ' --smc-check=all'; 3648 } 3649 3650 /* --vex-iropt-register-updates=allregs-at-mem-access is necessary for phpdbg watchpoint tests */ 3651 if ($this->version_3_8_0) { 3652 /* valgrind 3.3.0+ doesn't have --log-file-exactly option */ 3653 return "$vcmd --vex-iropt-register-updates=allregs-at-mem-access --log-file=$memcheck_filename $cmd"; 3654 } elseif ($this->version_3_3_0) { 3655 return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file=$memcheck_filename $cmd"; 3656 } else { 3657 return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file-exactly=$memcheck_filename $cmd"; 3658 } 3659 } 3660} 3661 3662main(); 3663