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: ce16b4fbf0aba210a673410ad92c7fc42ebd9365 $ */ 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: ce16b4fbf0aba210a673410ad92c7fc42ebd9365 $' . "\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['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 } 1277 1278 proc_close($proc); 1279 return $data; 1280} 1281 1282function run_all_tests($test_files, $env, $redir_tested = null) 1283{ 1284 global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx; 1285 // Parallel testing 1286 global $PHP_FAILED_TESTS, $workers, $workerID, $workerSock; 1287 1288 if ($workers !== null && !$workerID) { 1289 run_all_tests_parallel($test_files, $env, $redir_tested); 1290 return; 1291 } 1292 1293 foreach ($test_files as $name) { 1294 if (is_array($name)) { 1295 $index = "# $name[1]: $name[0]"; 1296 1297 if ($redir_tested) { 1298 $name = $name[0]; 1299 } 1300 } else if ($redir_tested) { 1301 $index = "# $redir_tested: $name"; 1302 } else { 1303 $index = $name; 1304 } 1305 $test_idx++; 1306 1307 if ($workerID) { 1308 $PHP_FAILED_TESTS = ['BORKED' => [], 'FAILED' => [], 'WARNED' => [], 'LEAKED' => [], 'XFAILED' => [], 'XLEAKED' => [], 'SLOW' => []]; 1309 ob_start(); 1310 } 1311 1312 $result = run_test($php, $name, $env); 1313 if ($workerID) { 1314 $resultText = ob_get_clean(); 1315 } 1316 1317 if (!is_array($name) && $result != 'REDIR') { 1318 if ($workerID) { 1319 send_message($workerSock, [ 1320 "type" => "test_result", 1321 "name" => $name, 1322 "index" => $index, 1323 "result" => $result, 1324 "text" => $resultText, 1325 "PHP_FAILED_TESTS" => $PHP_FAILED_TESTS 1326 ]); 1327 continue; 1328 } 1329 1330 $test_results[$index] = $result; 1331 if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { 1332 fwrite($failed_tests_file, "$index\n"); 1333 } 1334 if ($result_tests_file) { 1335 fwrite($result_tests_file, "$result\t$index\n"); 1336 } 1337 } 1338 } 1339} 1340 1341/** The heart of parallel testing. */ 1342function run_all_tests_parallel($test_files, $env, $redir_tested) { 1343 global $workers, $test_idx, $test_cnt, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $SHOW_ONLY_GROUPS; 1344 1345 // The PHP binary running run-tests.php, and run-tests.php itself 1346 // This PHP executable is *not* necessarily the same as the tested version 1347 $thisPHP = PHP_BINARY; 1348 $thisScript = __FILE__; 1349 1350 $workerProcs = []; 1351 $workerSocks = []; 1352 1353 echo "=====================================================================\n"; 1354 echo "========= WELCOME TO THE FUTURE: run-tests PARALLEL EDITION =========\n"; 1355 echo "=====================================================================\n"; 1356 1357 // Each test may specify a list of conflict keys. While a test that conflicts with 1358 // key K is running, no other test that conflicts with K may run. Conflict keys are 1359 // specified either in the --CONFLICTS-- section, or CONFLICTS file inside a directory. 1360 $dirConflictsWith = []; 1361 $fileConflictsWith = []; 1362 $sequentialTests = []; 1363 foreach ($test_files as $i => $file) { 1364 $contents = file_get_contents($file); 1365 if (preg_match('/^--CONFLICTS--(.+?)^--/ms', $contents, $matches)) { 1366 $conflicts = parse_conflicts($matches[1]); 1367 } else { 1368 // Cache per-directory conflicts in a separate map, so we compute these only once. 1369 $dir = dirname($file); 1370 if (!isset($dirConflictsWith[$dir])) { 1371 $dirConflicts = []; 1372 if (file_exists($dir . '/CONFLICTS')) { 1373 $contents = file_get_contents($dir . '/CONFLICTS'); 1374 $dirConflicts = parse_conflicts($contents); 1375 } 1376 $dirConflictsWith[$dir] = $dirConflicts; 1377 } 1378 $conflicts = $dirConflictsWith[$dir]; 1379 } 1380 1381 // For tests conflicting with "all", no other tests may run in parallel. We'll run these 1382 // tests separately at the end, when only one worker is left. 1383 if (in_array('all', $conflicts, true)) { 1384 $sequentialTests[] = $file; 1385 unset($test_files[$i]); 1386 } 1387 1388 $fileConflictsWith[$file] = $conflicts; 1389 } 1390 1391 // Some tests assume that they are executed in a certain order. We will be popping from 1392 // $test_files, so reverse its order here. This makes sure that order is preserved at least 1393 // for tests with a common conflict key. 1394 $test_files = array_reverse($test_files); 1395 1396 // To discover parallelization issues it is useful to randomize the test order. 1397 if ($shuffle) { 1398 shuffle($test_files); 1399 } 1400 1401 echo "Spawning workers… "; 1402 1403 // We use sockets rather than STDIN/STDOUT for comms because on Windows, 1404 // those can't be non-blocking for some reason. 1405 $listenSock = stream_socket_server("tcp://127.0.0.1:0") or error("Couldn't create socket on localhost."); 1406 $sockName = stream_socket_get_name($listenSock, false); 1407 // PHP is terrible and returns IPv6 addresses not enclosed by [] 1408 $portPos = strrpos($sockName, ":"); 1409 $sockHost = substr($sockName, 0, $portPos); 1410 if (FALSE !== strpos($sockHost, ":")) { 1411 $sockHost = "[$sockHost]"; 1412 } 1413 $sockPort = substr($sockName, $portPos + 1); 1414 $sockUri = "tcp://$sockHost:$sockPort"; 1415 1416 for ($i = 1; $i <= $workers; $i++) { 1417 $proc = proc_open( 1418 $thisPHP . ' ' . escapeshellarg($thisScript), 1419 [], // Inherit our stdin, stdout and stderr 1420 $pipes, 1421 NULL, 1422 $_ENV + [ 1423 "TEST_PHP_WORKER" => $i, 1424 "TEST_PHP_URI" => $sockUri, 1425 ], 1426 [ 1427 "suppress_errors" => TRUE, 1428 'create_new_console' => TRUE, 1429 ] 1430 ); 1431 if ($proc === FALSE) { 1432 kill_children($workerProcs); 1433 error("Failed to spawn worker $i"); 1434 } 1435 $workerProcs[$i] = $proc; 1436 1437 $workerSock = stream_socket_accept($listenSock, 5); 1438 if ($workerSock === FALSE) { 1439 kill_children($workerProcs); 1440 error("Failed to accept connection from worker $i"); 1441 } 1442 1443 $greeting = base64_encode(serialize([ 1444 "type" => "hello", 1445 "workerID" => $i, 1446 "GLOBALS" => $GLOBALS, 1447 "constants" => [ 1448 "INIT_DIR" => INIT_DIR, 1449 "TEST_PHP_SRCDIR" => TEST_PHP_SRCDIR, 1450 "PHP_QA_EMAIL" => PHP_QA_EMAIL, 1451 "QA_SUBMISSION_PAGE" => QA_SUBMISSION_PAGE, 1452 "QA_REPORTS_PAGE" => QA_REPORTS_PAGE, 1453 "TRAVIS_CI" => TRAVIS_CI 1454 ] 1455 ])) . "\n"; 1456 1457 stream_set_timeout($workerSock, 5); 1458 if (fwrite($workerSock, $greeting) === FALSE) { 1459 kill_children($workerProcs); 1460 error("Failed to send greeting to worker $i."); 1461 } 1462 1463 $rawReply = fgets($workerSock); 1464 if ($rawReply === FALSE) { 1465 kill_children($workerProcs); 1466 error("Failed to read greeting reply from worker $i."); 1467 } 1468 1469 $reply = unserialize(base64_decode($rawReply)); 1470 if (!$reply || $reply["type"] !== "hello_reply" || $reply["workerID"] !== $i) { 1471 kill_children($workerProcs); 1472 error("Greeting reply from worker $i unexpected or could not be decoded: '$rawReply'"); 1473 } 1474 1475 stream_set_timeout($workerSock, 0); 1476 stream_set_blocking($workerSock, FALSE); 1477 1478 $workerSocks[$i] = $workerSock; 1479 1480 echo "$i "; 1481 } 1482 echo "… done!\n"; 1483 echo "=====================================================================\n"; 1484 echo "\n"; 1485 1486 $rawMessageBuffers = []; 1487 $testsInProgress = 0; 1488 1489 // Map from conflict key to worker ID. 1490 $activeConflicts = []; 1491 // Tests waiting due to conflicts. Map from conflict key to array. 1492 $waitingTests = []; 1493 1494escape: 1495 while ($test_files || $sequentialTests || $testsInProgress > 0) { 1496 $toRead = array_values($workerSocks); 1497 $toWrite = NULL; 1498 $toExcept = NULL; 1499 if (stream_select($toRead, $toWrite, $toExcept, 10)) { 1500 foreach ($toRead as $workerSock) { 1501 $i = array_search($workerSock, $workerSocks); 1502 if ($i === FALSE) { 1503 kill_children($workerProcs); 1504 error("Could not find worker stdout in array of worker stdouts, THIS SHOULD NOT HAPPEN."); 1505 } 1506 while (FALSE !== ($rawMessage = fgets($workerSock))) { 1507 // work around fgets truncating things 1508 if (($rawMessageBuffers[$i] ?? '') !== '') { 1509 $rawMessage = $rawMessageBuffers[$i] . $rawMessage; 1510 $rawMessageBuffers[$i] = ''; 1511 } 1512 if (substr($rawMessage, -1) !== "\n") { 1513 $rawMessageBuffers[$i] = $rawMessage; 1514 continue; 1515 } 1516 1517 $message = unserialize(base64_decode($rawMessage)); 1518 if (!$message) { 1519 kill_children($workerProcs); 1520 $stuff = fread($workerSock, 65536); 1521 error("Could not decode message from worker $i: '$rawMessage$stuff'"); 1522 } 1523 1524 switch ($message["type"]) { 1525 case "tests_finished": 1526 $testsInProgress--; 1527 foreach ($activeConflicts as $key => $workerId) { 1528 if ($workerId === $i) { 1529 unset($activeConflicts[$key]); 1530 if (isset($waitingTests[$key])) { 1531 while ($test = array_pop($waitingTests[$key])) { 1532 $test_files[] = $test; 1533 } 1534 unset($waitingTests[$key]); 1535 } 1536 } 1537 } 1538 if (junit_enabled()) { 1539 junit_merge_results($message["junit"]); 1540 } 1541 // intentional fall-through 1542 case "ready": 1543 // Schedule sequential tests only once we are down to one worker. 1544 if (count($workerProcs) === 1 && $sequentialTests) { 1545 $test_files = array_merge($test_files, $sequentialTests); 1546 $sequentialTests = []; 1547 } 1548 // Batch multiple tests to reduce communication overhead. 1549 $files = []; 1550 $batchSize = $shuffle ? 4 : 32; 1551 while (count($files) <= $batchSize && $file = array_pop($test_files)) { 1552 foreach ($fileConflictsWith[$file] as $conflictKey) { 1553 if (isset($activeConflicts[$conflictKey])) { 1554 $waitingTests[$conflictKey][] = $file; 1555 continue 2; 1556 } 1557 } 1558 $files[] = $file; 1559 } 1560 if ($files) { 1561 foreach ($files as $file) { 1562 foreach ($fileConflictsWith[$file] as $conflictKey) { 1563 $activeConflicts[$conflictKey] = $i; 1564 } 1565 } 1566 $testsInProgress++; 1567 send_message($workerSocks[$i], [ 1568 "type" => "run_tests", 1569 "test_files" => $files, 1570 "env" => $env, 1571 "redir_tested" => $redir_tested 1572 ]); 1573 } else { 1574 proc_terminate($workerProcs[$i]); 1575 unset($workerProcs[$i]); 1576 unset($workerSocks[$i]); 1577 goto escape; 1578 } 1579 break; 1580 case "test_result": 1581 list($name, $index, $result, $resultText) = [$message["name"], $message["index"], $message["result"], $message["text"]]; 1582 foreach ($message["PHP_FAILED_TESTS"] as $category => $tests) { 1583 $PHP_FAILED_TESTS[$category] = array_merge($PHP_FAILED_TESTS[$category], $tests); 1584 } 1585 $test_idx++; 1586 1587 if (!$SHOW_ONLY_GROUPS) { 1588 clear_show_test(); 1589 } 1590 1591 echo $resultText; 1592 1593 if (!$SHOW_ONLY_GROUPS) { 1594 show_test($test_idx, count($workerProcs) . "/$workers concurrent test workers running"); 1595 } 1596 1597 if (!is_array($name) && $result != 'REDIR') { 1598 $test_results[$index] = $result; 1599 1600 if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { 1601 fwrite($failed_tests_file, "$index\n"); 1602 } 1603 if ($result_tests_file) { 1604 fwrite($result_tests_file, "$result\t$index\n"); 1605 } 1606 } 1607 break; 1608 case "error": 1609 kill_children($workerProcs); 1610 error("Worker $i reported error: $message[msg]"); 1611 break; 1612 case "php_error": 1613 kill_children($workerProcs); 1614 $error_consts = [ 1615 'E_ERROR', 1616 'E_WARNING', 1617 'E_PARSE', 1618 'E_NOTICE', 1619 'E_CORE_ERROR', 1620 'E_CORE_WARNING', 1621 'E_COMPILE_ERROR', 1622 'E_COMPILE_WARNING', 1623 'E_USER_ERROR', 1624 'E_USER_WARNING', 1625 'E_USER_NOTICE', 1626 'E_STRICT', // TODO Cleanup when removed from Zend Engine. 1627 'E_RECOVERABLE_ERROR', 1628 'E_USER_DEPRECATED' 1629 ]; 1630 $error_consts = array_combine(array_map('constant', $error_consts), $error_consts); 1631 error("Worker $i reported unexpected {$error_consts[$message['errno']]}: $message[errstr] in $message[errfile] on line $message[errline]"); 1632 default: 1633 kill_children($workerProcs); 1634 error("Unrecognised message type '$message[type]' from worker $i"); 1635 } 1636 } 1637 } 1638 } 1639 } 1640 1641 if (!$SHOW_ONLY_GROUPS) { 1642 clear_show_test(); 1643 } 1644 1645 kill_children($workerProcs); 1646 1647 if ($testsInProgress < 0) { 1648 error("$testsInProgress test batches “in progress”, which is less than zero. THIS SHOULD NOT HAPPEN."); 1649 } 1650} 1651 1652function send_message($stream, array $message) { 1653 $blocking = stream_get_meta_data($stream)["blocked"]; 1654 stream_set_blocking($stream, true); 1655 fwrite($stream, base64_encode(serialize($message)) . "\n"); 1656 stream_set_blocking($stream, $blocking); 1657} 1658 1659function kill_children(array $children) { 1660 foreach ($children as $child) { 1661 if ($child) { 1662 proc_terminate($child); 1663 } 1664 } 1665} 1666 1667function run_worker() { 1668 global $workerID, $workerSock; 1669 1670 $sockUri = getenv("TEST_PHP_URI"); 1671 1672 $workerSock = stream_socket_client($sockUri, $_, $_, 5) or error("Couldn't connect to $sockUri"); 1673 1674 $greeting = fgets($workerSock); 1675 $greeting = unserialize(base64_decode($greeting)) or die("Could not decode greeting\n"); 1676 if ($greeting["type"] !== "hello" || $greeting["workerID"] !== $workerID) { 1677 error("Unexpected greeting of type $greeting[type] and for worker $greeting[workerID]"); 1678 } 1679 1680 set_error_handler(function ($errno, $errstr, $errfile, $errline) use ($workerSock) { 1681 if (error_reporting() & $errno) { 1682 send_message($workerSock, compact('errno', 'errstr', 'errfile', 'errline') + [ 1683 'type' => 'php_error' 1684 ]); 1685 } 1686 1687 return true; 1688 }); 1689 1690 foreach ($greeting["GLOBALS"] as $var => $value) { 1691 if ($var !== "workerID" && $var !== "workerSock" && $var !== "GLOBALS") { 1692 $GLOBALS[$var] = $value; 1693 } 1694 } 1695 foreach ($greeting["constants"] as $const => $value) { 1696 define($const, $value); 1697 } 1698 1699 send_message($workerSock, [ 1700 "type" => "hello_reply", 1701 "workerID" => $workerID 1702 ]); 1703 1704 send_message($workerSock, [ 1705 "type" => "ready" 1706 ]); 1707 1708 while (($command = fgets($workerSock))) { 1709 $command = unserialize(base64_decode($command)); 1710 1711 switch ($command["type"]) { 1712 case "run_tests": 1713 run_all_tests($command["test_files"], $command["env"], $command["redir_tested"]); 1714 send_message($workerSock, [ 1715 "type" => "tests_finished", 1716 "junit" => junit_enabled() ? $GLOBALS['JUNIT'] : null, 1717 ]); 1718 junit_init(); 1719 break; 1720 default: 1721 send_message($workerSock, [ 1722 "type" => "error", 1723 "msg" => "Unrecognised message type: $command[type]" 1724 ]); 1725 break 2; 1726 } 1727 } 1728} 1729 1730// 1731// Show file or result block 1732// 1733function show_file_block($file, $block, $section = null) 1734{ 1735 global $cfg; 1736 1737 if ($cfg['show'][$file]) { 1738 1739 if (is_null($section)) { 1740 $section = strtoupper($file); 1741 } 1742 1743 echo "\n========" . $section . "========\n"; 1744 echo rtrim($block); 1745 echo "\n========DONE========\n"; 1746 } 1747} 1748 1749// 1750// Run an individual test case. 1751// 1752function run_test($php, $file, $env) 1753{ 1754 global $log_format, $ini_overwrites, $PHP_FAILED_TESTS; 1755 global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx; 1756 global $valgrind, $temp_source, $temp_target, $cfg, $environment; 1757 global $no_clean; 1758 global $SHOW_ONLY_GROUPS; 1759 global $no_file_cache; 1760 global $slow_min_ms; 1761 global $preload; 1762 // Parallel testing 1763 global $workerID; 1764 $temp_filenames = null; 1765 $org_file = $file; 1766 1767 if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) { 1768 $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE']; 1769 } 1770 1771 if (isset($env['TEST_PHPDBG_EXECUTABLE'])) { 1772 $phpdbg = $env['TEST_PHPDBG_EXECUTABLE']; 1773 } 1774 1775 if (is_array($file)) { 1776 $file = $file[0]; 1777 } 1778 1779 if ($DETAILED) echo " 1780================= 1781TEST $file 1782"; 1783 1784 // Load the sections of the test file. 1785 $section_text = array('TEST' => ''); 1786 1787 $fp = fopen($file, "rb") or error("Cannot open test file: $file"); 1788 1789 $bork_info = null; 1790 1791 if (!feof($fp)) { 1792 $line = fgets($fp); 1793 1794 if ($line === false) { 1795 $bork_info = "cannot read test"; 1796 } 1797 } else { 1798 $bork_info = "empty test [$file]"; 1799 } 1800 if ($bork_info === null && strncmp('--TEST--', $line, 8)) { 1801 $bork_info = "tests must start with --TEST-- [$file]"; 1802 } 1803 1804 $section = 'TEST'; 1805 $secfile = false; 1806 $secdone = false; 1807 1808 while (!feof($fp)) { 1809 $line = fgets($fp); 1810 1811 if ($line === false) { 1812 break; 1813 } 1814 1815 // Match the beginning of a section. 1816 if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { 1817 $section = (string)$r[1]; 1818 1819 if (isset($section_text[$section]) && $section_text[$section]) { 1820 $bork_info = "duplicated $section section"; 1821 } 1822 1823 // check for unknown sections 1824 if (!in_array($section, array( 1825 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', 1826 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', 1827 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', 1828 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', 1829 'INI', 'ENV', 'EXTENSIONS', 1830 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', 1831 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', 1832 ))) { 1833 $bork_info = 'Unknown section "' . $section . '"'; 1834 } 1835 1836 $section_text[$section] = ''; 1837 $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL'; 1838 $secdone = false; 1839 continue; 1840 } 1841 1842 // Add to the section text. 1843 if (!$secdone) { 1844 $section_text[$section] .= $line; 1845 } 1846 1847 // End of actual test? 1848 if ($secfile && preg_match('/^===DONE===\s*$/', $line)) { 1849 $secdone = true; 1850 } 1851 } 1852 1853 // the redirect section allows a set of tests to be reused outside of 1854 // a given test dir 1855 if ($bork_info === null) { 1856 if (isset($section_text['REDIRECTTEST'])) { 1857 1858 if ($IN_REDIRECT) { 1859 $bork_info = "Can't redirect a test from within a redirected test"; 1860 } 1861 1862 } else { 1863 1864 if (!isset($section_text['PHPDBG']) && isset($section_text['FILE']) + isset($section_text['FILEEOF']) + isset($section_text['FILE_EXTERNAL']) != 1) { 1865 $bork_info = "missing section --FILE--"; 1866 } 1867 1868 if (isset($section_text['FILEEOF'])) { 1869 $section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']); 1870 unset($section_text['FILEEOF']); 1871 } 1872 1873 foreach (array('FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) { 1874 $key = $prefix . '_EXTERNAL'; 1875 1876 if (isset($section_text[$key])) { 1877 // don't allow tests to retrieve files from anywhere but this subdirectory 1878 $section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key])); 1879 1880 if (file_exists($section_text[$key])) { 1881 $section_text[$prefix] = file_get_contents($section_text[$key], FILE_BINARY); 1882 unset($section_text[$key]); 1883 } else { 1884 $bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]); 1885 } 1886 } 1887 } 1888 1889 if ((isset($section_text['EXPECT']) + isset($section_text['EXPECTF']) + isset($section_text['EXPECTREGEX'])) != 1) { 1890 $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"; 1891 } 1892 } 1893 } 1894 fclose($fp); 1895 1896 $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); 1897 $tested_file = $shortname; 1898 1899 if ($bork_info !== null) { 1900 show_result("BORK", $bork_info, $tested_file); 1901 $PHP_FAILED_TESTS['BORKED'][] = array( 1902 'name' => $file, 1903 'test_name' => '', 1904 'output' => '', 1905 'diff' => '', 1906 'info' => "$bork_info [$file]", 1907 ); 1908 1909 junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info); 1910 return 'BORKED'; 1911 } 1912 1913 if (isset($section_text['CAPTURE_STDIO'])) { 1914 $captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false; 1915 $captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false; 1916 $captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false; 1917 } else { 1918 $captureStdIn = true; 1919 $captureStdOut = true; 1920 $captureStdErr = true; 1921 } 1922 if ($captureStdOut && $captureStdErr) { 1923 $cmdRedirect = ' 2>&1'; 1924 } else { 1925 $cmdRedirect = ''; 1926 } 1927 1928 $tested = trim($section_text['TEST']); 1929 1930 /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ 1931 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'])) { 1932 if (isset($php_cgi)) { 1933 $php = $php_cgi . ' -C '; 1934 } else if (!strncasecmp(PHP_OS, "win", 3) && file_exists(dirname($php) . "/php-cgi.exe")) { 1935 $php = realpath(dirname($php) . "/php-cgi.exe") . ' -C '; 1936 } else { 1937 if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) { 1938 $php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C '; 1939 } else if (file_exists("./sapi/cgi/php-cgi")) { 1940 $php = realpath("./sapi/cgi/php-cgi") . ' -C '; 1941 } else if (file_exists(dirname($php) . "/php-cgi")) { 1942 $php = realpath(dirname($php) . "/php-cgi") . ' -C '; 1943 } else { 1944 show_result('SKIP', $tested, $tested_file, "reason: CGI not available"); 1945 1946 junit_init_suite(junit_get_suitename_for($shortname)); 1947 junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available'); 1948 return 'SKIPPED'; 1949 } 1950 } 1951 $uses_cgi = true; 1952 } 1953 1954 /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */ 1955 $extra_options = ''; 1956 if (array_key_exists('PHPDBG', $section_text)) { 1957 if (!isset($section_text['STDIN'])) { 1958 $section_text['STDIN'] = $section_text['PHPDBG'] . "\n"; 1959 } 1960 1961 if (isset($phpdbg)) { 1962 $php = $phpdbg . ' -qIb'; 1963 1964 // Additional phpdbg command line options for sections that need to 1965 // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN. 1966 $extra_options = '-rr'; 1967 } else { 1968 show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available"); 1969 1970 junit_init_suite(junit_get_suitename_for($shortname)); 1971 junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available'); 1972 return 'SKIPPED'; 1973 } 1974 } 1975 1976 if (!$SHOW_ONLY_GROUPS && !$workerID) { 1977 show_test($test_idx, $shortname); 1978 } 1979 1980 if (is_array($IN_REDIRECT)) { 1981 $temp_dir = $test_dir = $IN_REDIRECT['dir']; 1982 } else { 1983 $temp_dir = $test_dir = realpath(dirname($file)); 1984 } 1985 1986 if ($temp_source && $temp_target) { 1987 $temp_dir = str_replace($temp_source, $temp_target, $temp_dir); 1988 } 1989 1990 $main_file_name = basename($file, 'phpt'); 1991 1992 $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff'; 1993 $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log'; 1994 $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp'; 1995 $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out'; 1996 $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem'; 1997 $sh_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh'; 1998 $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; 1999 $test_file = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; 2000 $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; 2001 $test_skipif = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; 2002 $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; 2003 $test_clean = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; 2004 $preload_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'preload.php'; 2005 $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'post'; 2006 $tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't'; 2007 2008 if ($temp_source && $temp_target) { 2009 $temp_skipif .= 's'; 2010 $temp_file .= 's'; 2011 $temp_clean .= 's'; 2012 $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps'; 2013 2014 if (!is_dir(dirname($copy_file))) { 2015 mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file)); 2016 } 2017 2018 if (isset($section_text['FILE'])) { 2019 save_text($copy_file, $section_text['FILE']); 2020 } 2021 2022 $temp_filenames = array( 2023 'file' => $copy_file, 2024 'diff' => $diff_filename, 2025 'log' => $log_filename, 2026 'exp' => $exp_filename, 2027 'out' => $output_filename, 2028 'mem' => $memcheck_filename, 2029 'sh' => $sh_filename, 2030 'php' => $temp_file, 2031 'skip' => $temp_skipif, 2032 'clean' => $temp_clean 2033 ); 2034 } 2035 2036 if (is_array($IN_REDIRECT)) { 2037 $tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']); 2038 $tested_file = $tmp_relative_file; 2039 $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $tested_file); 2040 } 2041 2042 // unlink old test results 2043 @unlink($diff_filename); 2044 @unlink($log_filename); 2045 @unlink($exp_filename); 2046 @unlink($output_filename); 2047 @unlink($memcheck_filename); 2048 @unlink($sh_filename); 2049 @unlink($temp_file); 2050 @unlink($test_file); 2051 @unlink($temp_skipif); 2052 @unlink($test_skipif); 2053 @unlink($tmp_post); 2054 @unlink($temp_clean); 2055 @unlink($test_clean); 2056 @unlink($preload_filename); 2057 2058 // Reset environment from any previous test. 2059 $env['REDIRECT_STATUS'] = ''; 2060 $env['QUERY_STRING'] = ''; 2061 $env['PATH_TRANSLATED'] = ''; 2062 $env['SCRIPT_FILENAME'] = ''; 2063 $env['REQUEST_METHOD'] = ''; 2064 $env['CONTENT_TYPE'] = ''; 2065 $env['CONTENT_LENGTH'] = ''; 2066 $env['TZ'] = ''; 2067 2068 if (!empty($section_text['ENV'])) { 2069 2070 foreach (explode("\n", trim($section_text['ENV'])) as $e) { 2071 $e = explode('=', trim($e), 2); 2072 2073 if (!empty($e[0]) && isset($e[1])) { 2074 $env[$e[0]] = $e[1]; 2075 } 2076 } 2077 } 2078 2079 // Default ini settings 2080 $ini_settings = $workerID ? array('opcache.cache_id' => "worker$workerID") : array(); 2081 2082 // Additional required extensions 2083 if (array_key_exists('EXTENSIONS', $section_text)) { 2084 $ext_params = array(); 2085 settings2array($ini_overwrites, $ext_params); 2086 $ext_params = settings2params($ext_params); 2087 $ext_dir = `$php $pass_options $extra_options $ext_params -d display_errors=0 -r "echo ini_get('extension_dir');"`; 2088 $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); 2089 $loaded = explode(",", `$php $pass_options $extra_options $ext_params -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`); 2090 $ext_prefix = substr(PHP_OS, 0, 3) === "WIN" ? "php_" : ""; 2091 foreach ($extensions as $req_ext) { 2092 if (!in_array($req_ext, $loaded)) { 2093 if ($req_ext == 'opcache') { 2094 $ini_settings['zend_extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; 2095 } else { 2096 $ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; 2097 } 2098 } 2099 } 2100 } 2101 2102 // additional ini overwrites 2103 //$ini_overwrites[] = 'setting=value'; 2104 settings2array($ini_overwrites, $ini_settings); 2105 2106 $orig_ini_settings = settings2params($ini_settings); 2107 2108 // Any special ini settings 2109 // these may overwrite the test defaults... 2110 if (array_key_exists('INI', $section_text)) { 2111 $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); 2112 $section_text['INI'] = str_replace('{TMP}', sys_get_temp_dir(), $section_text['INI']); 2113 settings2array(preg_split("/[\n\r]+/", $section_text['INI']), $ini_settings); 2114 } 2115 2116 $ini_settings = settings2params($ini_settings); 2117 2118 $env['TEST_PHP_EXTRA_ARGS'] = $pass_options . ' ' . $ini_settings; 2119 2120 // Check if test should be skipped. 2121 $info = ''; 2122 $warn = false; 2123 2124 if (array_key_exists('SKIPIF', $section_text)) { 2125 2126 if (trim($section_text['SKIPIF'])) { 2127 show_file_block('skip', $section_text['SKIPIF']); 2128 save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif); 2129 $extra = substr(PHP_OS, 0, 3) !== "WIN" ? 2130 "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; 2131 2132 if ($valgrind) { 2133 $env['USE_ZEND_ALLOC'] = '0'; 2134 $env['ZEND_DONT_UNLOAD_MODULES'] = 1; 2135 } 2136 2137 junit_start_timer($shortname); 2138 2139 $output = system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=0 \"$test_skipif\"", $env); 2140 2141 junit_finish_timer($shortname); 2142 2143 if (!$cfg['keep']['skip']) { 2144 @unlink($test_skipif); 2145 } 2146 2147 if (!strncasecmp('skip', ltrim($output), 4)) { 2148 2149 if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) { 2150 show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); 2151 } else { 2152 show_result('SKIP', $tested, $tested_file, '', $temp_filenames); 2153 } 2154 2155 if (!$cfg['keep']['skip']) { 2156 @unlink($test_skipif); 2157 } 2158 2159 $message = !empty($m[1]) ? $m[1] : ''; 2160 junit_mark_test_as('SKIP', $shortname, $tested, null, $message); 2161 return 'SKIPPED'; 2162 } 2163 2164 if (!strncasecmp('info', ltrim($output), 4)) { 2165 if (preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) { 2166 $info = " (info: $m[1])"; 2167 } 2168 } 2169 2170 if (!strncasecmp('warn', ltrim($output), 4)) { 2171 if (preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) { 2172 $warn = true; /* only if there is a reason */ 2173 $info = " (warn: $m[1])"; 2174 } 2175 } 2176 2177 if (!strncasecmp('xfail', ltrim($output), 5)) { 2178 // Pretend we have an XFAIL section 2179 $section_text['XFAIL'] = trim(substr(ltrim($output), 5)); 2180 } 2181 } 2182 } 2183 2184 if (!extension_loaded("zlib") 2185 && (array_key_exists("GZIP_POST", $section_text) 2186 || array_key_exists("DEFLATE_POST", $section_text))) { 2187 $message = "ext/zlib required"; 2188 show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames); 2189 junit_mark_test_as('SKIP', $shortname, $tested, null, $message); 2190 return 'SKIPPED'; 2191 } 2192 2193 if (isset($section_text['REDIRECTTEST'])) { 2194 $test_files = array(); 2195 2196 $IN_REDIRECT = eval($section_text['REDIRECTTEST']); 2197 $IN_REDIRECT['via'] = "via [$shortname]\n\t"; 2198 $IN_REDIRECT['dir'] = realpath(dirname($file)); 2199 $IN_REDIRECT['prefix'] = trim($section_text['TEST']); 2200 2201 if (!empty($IN_REDIRECT['TESTS'])) { 2202 2203 if (is_array($org_file)) { 2204 $test_files[] = $org_file[1]; 2205 } else { 2206 $GLOBALS['test_files'] = $test_files; 2207 find_files($IN_REDIRECT['TESTS']); 2208 2209 foreach ($GLOBALS['test_files'] as $f) { 2210 $test_files[] = array($f, $file); 2211 } 2212 } 2213 $test_cnt += count($test_files) - 1; 2214 $test_idx--; 2215 2216 show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file); 2217 2218 // set up environment 2219 $redirenv = array_merge($environment, $IN_REDIRECT['ENV']); 2220 $redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR; 2221 2222 usort($test_files, "test_sort"); 2223 run_all_tests($test_files, $redirenv, $tested); 2224 2225 show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file); 2226 2227 // a redirected test never fails 2228 $IN_REDIRECT = false; 2229 2230 junit_mark_test_as('PASS', $shortname, $tested); 2231 return 'REDIR'; 2232 2233 } else { 2234 2235 $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; 2236 show_result("BORK", $bork_info, '', $temp_filenames); 2237 $PHP_FAILED_TESTS['BORKED'][] = array( 2238 'name' => $file, 2239 'test_name' => '', 2240 'output' => '', 2241 'diff' => '', 2242 'info' => "$bork_info [$file]", 2243 ); 2244 } 2245 } 2246 2247 if (is_array($org_file) || isset($section_text['REDIRECTTEST'])) { 2248 2249 if (is_array($org_file)) { 2250 $file = $org_file[0]; 2251 } 2252 2253 $bork_info = "Redirected test did not contain redirection info"; 2254 show_result("BORK", $bork_info, '', $temp_filenames); 2255 $PHP_FAILED_TESTS['BORKED'][] = array( 2256 'name' => $file, 2257 'test_name' => '', 2258 'output' => '', 2259 'diff' => '', 2260 'info' => "$bork_info [$file]", 2261 ); 2262 2263 junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info); 2264 2265 return 'BORKED'; 2266 } 2267 2268 // We've satisfied the preconditions - run the test! 2269 if (isset($section_text['FILE'])) { 2270 show_file_block('php', $section_text['FILE'], 'TEST'); 2271 save_text($test_file, $section_text['FILE'], $temp_file); 2272 } else { 2273 $test_file = $temp_file = ""; 2274 } 2275 2276 if (array_key_exists('GET', $section_text)) { 2277 $query_string = trim($section_text['GET']); 2278 } else { 2279 $query_string = ''; 2280 } 2281 2282 $env['REDIRECT_STATUS'] = '1'; 2283 if (empty($env['QUERY_STRING'])) { 2284 $env['QUERY_STRING'] = $query_string; 2285 } 2286 if (empty($env['PATH_TRANSLATED'])) { 2287 $env['PATH_TRANSLATED'] = $test_file; 2288 } 2289 if (empty($env['SCRIPT_FILENAME'])) { 2290 $env['SCRIPT_FILENAME'] = $test_file; 2291 } 2292 2293 if (array_key_exists('COOKIE', $section_text)) { 2294 $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); 2295 } else { 2296 $env['HTTP_COOKIE'] = ''; 2297 } 2298 2299 $args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : ''; 2300 2301 if ($preload && !empty($test_file)) { 2302 save_text($preload_filename, "<?php opcache_compile_file('$test_file');"); 2303 $local_pass_options = $pass_options; 2304 unset($pass_options); 2305 $pass_options = $local_pass_options; 2306 $pass_options .= " -d opcache.preload=" . $preload_filename; 2307 } 2308 2309 if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) { 2310 2311 $post = trim($section_text['POST_RAW']); 2312 $raw_lines = explode("\n", $post); 2313 2314 $request = ''; 2315 $started = false; 2316 2317 foreach ($raw_lines as $line) { 2318 2319 if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) { 2320 $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); 2321 continue; 2322 } 2323 2324 if ($started) { 2325 $request .= "\n"; 2326 } 2327 2328 $started = true; 2329 $request .= $line; 2330 } 2331 2332 $env['CONTENT_LENGTH'] = strlen($request); 2333 $env['REQUEST_METHOD'] = 'POST'; 2334 2335 if (empty($request)) { 2336 junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request'); 2337 return 'BORKED'; 2338 } 2339 2340 save_text($tmp_post, $request); 2341 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2342 2343 } elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) { 2344 2345 $post = trim($section_text['PUT']); 2346 $raw_lines = explode("\n", $post); 2347 2348 $request = ''; 2349 $started = false; 2350 2351 foreach ($raw_lines as $line) { 2352 2353 if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) { 2354 $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); 2355 continue; 2356 } 2357 2358 if ($started) { 2359 $request .= "\n"; 2360 } 2361 2362 $started = true; 2363 $request .= $line; 2364 } 2365 2366 $env['CONTENT_LENGTH'] = strlen($request); 2367 $env['REQUEST_METHOD'] = 'PUT'; 2368 2369 if (empty($request)) { 2370 junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request'); 2371 return 'BORKED'; 2372 } 2373 2374 save_text($tmp_post, $request); 2375 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2376 2377 } else if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { 2378 2379 $post = trim($section_text['POST']); 2380 $content_length = strlen($post); 2381 save_text($tmp_post, $post); 2382 2383 $env['REQUEST_METHOD'] = 'POST'; 2384 if (empty($env['CONTENT_TYPE'])) { 2385 $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 2386 } 2387 2388 if (empty($env['CONTENT_LENGTH'])) { 2389 $env['CONTENT_LENGTH'] = $content_length; 2390 } 2391 2392 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2393 2394 } else if (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) { 2395 2396 $post = trim($section_text['GZIP_POST']); 2397 $post = gzencode($post, 9, FORCE_GZIP); 2398 $env['HTTP_CONTENT_ENCODING'] = 'gzip'; 2399 2400 save_text($tmp_post, $post); 2401 $content_length = strlen($post); 2402 2403 $env['REQUEST_METHOD'] = 'POST'; 2404 $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 2405 $env['CONTENT_LENGTH'] = $content_length; 2406 2407 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2408 2409 } else if (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) { 2410 $post = trim($section_text['DEFLATE_POST']); 2411 $post = gzcompress($post, 9); 2412 $env['HTTP_CONTENT_ENCODING'] = 'deflate'; 2413 save_text($tmp_post, $post); 2414 $content_length = strlen($post); 2415 2416 $env['REQUEST_METHOD'] = 'POST'; 2417 $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 2418 $env['CONTENT_LENGTH'] = $content_length; 2419 2420 $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 2421 2422 } else { 2423 2424 $env['REQUEST_METHOD'] = 'GET'; 2425 $env['CONTENT_TYPE'] = ''; 2426 $env['CONTENT_LENGTH'] = ''; 2427 2428 $cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args$cmdRedirect"; 2429 } 2430 2431 if ($valgrind) { 2432 $env['USE_ZEND_ALLOC'] = '0'; 2433 $env['ZEND_DONT_UNLOAD_MODULES'] = 1; 2434 2435 $cmd = $valgrind->wrapCommand($cmd, $memcheck_filename, strpos($test_file, "pcre") !== false); 2436 } 2437 2438 if ($DETAILED) echo " 2439CONTENT_LENGTH = " . $env['CONTENT_LENGTH'] . " 2440CONTENT_TYPE = " . $env['CONTENT_TYPE'] . " 2441PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . " 2442QUERY_STRING = " . $env['QUERY_STRING'] . " 2443REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . " 2444REQUEST_METHOD = " . $env['REQUEST_METHOD'] . " 2445SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . " 2446HTTP_COOKIE = " . $env['HTTP_COOKIE'] . " 2447COMMAND $cmd 2448"; 2449 2450 junit_start_timer($shortname); 2451 $hrtime = hrtime(); 2452 $startTime = $hrtime[0] * 1000000000 + $hrtime[1]; 2453 2454 $out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr); 2455 2456 junit_finish_timer($shortname); 2457 $hrtime = hrtime(); 2458 $time = $hrtime[0] * 1000000000 + $hrtime[1] - $startTime; 2459 if ($time >= $slow_min_ms * 1000000) { 2460 $PHP_FAILED_TESTS['SLOW'][] = array( 2461 'name' => $file, 2462 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 2463 'output' => '', 2464 'diff' => '', 2465 'info' => $time / 1000000000, 2466 ); 2467 } 2468 2469 if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) { 2470 2471 if (trim($section_text['CLEAN'])) { 2472 show_file_block('clean', $section_text['CLEAN']); 2473 save_text($test_clean, trim($section_text['CLEAN']), $temp_clean); 2474 2475 if (!$no_clean) { 2476 $extra = substr(PHP_OS, 0, 3) !== "WIN" ? 2477 "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; 2478 system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env); 2479 } 2480 2481 if (!$cfg['keep']['clean']) { 2482 @unlink($test_clean); 2483 } 2484 } 2485 } 2486 2487 @unlink($preload_filename); 2488 2489 $leaked = false; 2490 $passed = false; 2491 2492 if ($valgrind) { // leak check 2493 $leaked = filesize($memcheck_filename) > 0; 2494 2495 if (!$leaked) { 2496 @unlink($memcheck_filename); 2497 } 2498 } 2499 2500 // Does the output match what is expected? 2501 $output = preg_replace("/\r\n/", "\n", trim($out)); 2502 2503 /* when using CGI, strip the headers from the output */ 2504 $headers = array(); 2505 2506 if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { 2507 $output = trim($match[2]); 2508 $rh = preg_split("/[\n\r]+/", $match[1]); 2509 2510 foreach ($rh as $line) { 2511 if (strpos($line, ':') !== false) { 2512 $line = explode(':', $line, 2); 2513 $headers[trim($line[0])] = trim($line[1]); 2514 } 2515 } 2516 } 2517 2518 $failed_headers = false; 2519 2520 if (isset($section_text['EXPECTHEADERS'])) { 2521 $want = array(); 2522 $wanted_headers = array(); 2523 $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); 2524 2525 foreach ($lines as $line) { 2526 if (strpos($line, ':') !== false) { 2527 $line = explode(':', $line, 2); 2528 $want[trim($line[0])] = trim($line[1]); 2529 $wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]); 2530 } 2531 } 2532 2533 $output_headers = array(); 2534 2535 foreach ($want as $k => $v) { 2536 2537 if (isset($headers[$k])) { 2538 $output_headers[] = $k . ': ' . $headers[$k]; 2539 } 2540 2541 if (!isset($headers[$k]) || $headers[$k] != $v) { 2542 $failed_headers = true; 2543 } 2544 } 2545 2546 ksort($wanted_headers); 2547 $wanted_headers = implode("\n", $wanted_headers); 2548 ksort($output_headers); 2549 $output_headers = implode("\n", $output_headers); 2550 } 2551 2552 show_file_block('out', $output); 2553 2554 if ($preload) { 2555 $output = trim(preg_replace("/\n?Warning: Can't preload [^\n]*\n?/", "", $output)); 2556 } 2557 2558 if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { 2559 2560 if (isset($section_text['EXPECTF'])) { 2561 $wanted = trim($section_text['EXPECTF']); 2562 } else { 2563 $wanted = trim($section_text['EXPECTREGEX']); 2564 } 2565 2566 show_file_block('exp', $wanted); 2567 $wanted_re = preg_replace('/\r\n/', "\n", $wanted); 2568 2569 if (isset($section_text['EXPECTF'])) { 2570 2571 // do preg_quote, but miss out any %r delimited sections 2572 $temp = ""; 2573 $r = "%r"; 2574 $startOffset = 0; 2575 $length = strlen($wanted_re); 2576 while ($startOffset < $length) { 2577 $start = strpos($wanted_re, $r, $startOffset); 2578 if ($start !== false) { 2579 // we have found a start tag 2580 $end = strpos($wanted_re, $r, $start + 2); 2581 if ($end === false) { 2582 // unbalanced tag, ignore it. 2583 $end = $start = $length; 2584 } 2585 } else { 2586 // no more %r sections 2587 $start = $end = $length; 2588 } 2589 // quote a non re portion of the string 2590 $temp .= preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/'); 2591 // add the re unquoted. 2592 if ($end > $start) { 2593 $temp .= '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')'; 2594 } 2595 $startOffset = $end + 2; 2596 } 2597 $wanted_re = $temp; 2598 2599 // Stick to basics 2600 $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re); 2601 $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re); 2602 $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re); 2603 $wanted_re = str_replace('%a', '.+', $wanted_re); 2604 $wanted_re = str_replace('%A', '.*', $wanted_re); 2605 $wanted_re = str_replace('%w', '\s*', $wanted_re); 2606 $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re); 2607 $wanted_re = str_replace('%d', '\d+', $wanted_re); 2608 $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re); 2609 $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re); 2610 $wanted_re = str_replace('%c', '.', $wanted_re); 2611 // %f allows two points "-.0.0" but that is the best *simple* expression 2612 } 2613/* DEBUG YOUR REGEX HERE 2614 var_dump($wanted_re); 2615 print(str_repeat('=', 80) . "\n"); 2616 var_dump($output); 2617 */ 2618 if (preg_match("/^$wanted_re\$/s", $output)) { 2619 $passed = true; 2620 if (!$cfg['keep']['php']) { 2621 @unlink($test_file); 2622 } 2623 @unlink($tmp_post); 2624 2625 if (!$leaked && !$failed_headers) { 2626 if (isset($section_text['XFAIL'])) { 2627 $warn = true; 2628 $info = " (warn: XFAIL section but test passes)"; 2629 } else if (isset($section_text['XLEAK'])) { 2630 $warn = true; 2631 $info = " (warn: XLEAK section but test passes)"; 2632 } else { 2633 show_result("PASS", $tested, $tested_file, '', $temp_filenames); 2634 junit_mark_test_as('PASS', $shortname, $tested); 2635 return 'PASSED'; 2636 } 2637 } 2638 } 2639 2640 } else { 2641 2642 $wanted = trim($section_text['EXPECT']); 2643 $wanted = preg_replace('/\r\n/', "\n", $wanted); 2644 show_file_block('exp', $wanted); 2645 2646 // compare and leave on success 2647 if (!strcmp($output, $wanted)) { 2648 $passed = true; 2649 2650 if (!$cfg['keep']['php']) { 2651 @unlink($test_file); 2652 } 2653 @unlink($tmp_post); 2654 2655 if (!$leaked && !$failed_headers) { 2656 if (isset($section_text['XFAIL'])) { 2657 $warn = true; 2658 $info = " (warn: XFAIL section but test passes)"; 2659 } elseif (isset($section_text['XLEAK'])) { 2660 $warn = true; 2661 $info = " (warn: XLEAK section but test passes)"; 2662 } else { 2663 show_result("PASS", $tested, $tested_file, '', $temp_filenames); 2664 junit_mark_test_as('PASS', $shortname, $tested); 2665 return 'PASSED'; 2666 } 2667 } 2668 } 2669 2670 $wanted_re = null; 2671 } 2672 2673 // Test failed so we need to report details. 2674 if ($failed_headers) { 2675 $passed = false; 2676 $wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted; 2677 $output = $output_headers . "\n--HEADERS--\n" . $output; 2678 2679 if (isset($wanted_re)) { 2680 $wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re; 2681 } 2682 } 2683 2684 if ($leaked) { 2685 $restype[] = isset($section_text['XLEAK']) ? 2686 'XLEAK' : 'LEAK'; 2687 } 2688 2689 if ($warn) { 2690 $restype[] = 'WARN'; 2691 } 2692 2693 if (!$passed) { 2694 if (isset($section_text['XFAIL'])) { 2695 $restype[] = 'XFAIL'; 2696 $info = ' XFAIL REASON: ' . rtrim($section_text['XFAIL']); 2697 } else if (isset($section_text['XLEAK'])) { 2698 $restype[] = 'XLEAK'; 2699 $info = ' XLEAK REASON: ' . rtrim($section_text['XLEAK']); 2700 } else { 2701 $restype[] = 'FAIL'; 2702 } 2703 } 2704 2705 if (!$passed) { 2706 2707 // write .exp 2708 if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted, FILE_BINARY) === false) { 2709 error("Cannot create expected test output - $exp_filename"); 2710 } 2711 2712 // write .out 2713 if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output, FILE_BINARY) === false) { 2714 error("Cannot create test output - $output_filename"); 2715 } 2716 2717 // write .diff 2718 $diff = generate_diff($wanted, $wanted_re, $output); 2719 if (is_array($IN_REDIRECT)) { 2720 $orig_shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); 2721 $diff = "# original source file: $orig_shortname\n" . $diff; 2722 } 2723 show_file_block('diff', $diff); 2724 if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff, FILE_BINARY) === false) { 2725 error("Cannot create test diff - $diff_filename"); 2726 } 2727 2728 // write .sh 2729 if (strpos($log_format, 'S') !== false && file_put_contents($sh_filename, "#!/bin/sh 2730 2731{$cmd} 2732", FILE_BINARY) === false) { 2733 error("Cannot create test shell script - $sh_filename"); 2734 } 2735 chmod($sh_filename, 0755); 2736 2737 // write .log 2738 if (strpos($log_format, 'L') !== false && file_put_contents($log_filename, " 2739---- EXPECTED OUTPUT 2740$wanted 2741---- ACTUAL OUTPUT 2742$output 2743---- FAILED 2744", FILE_BINARY) === false) { 2745 error("Cannot create test log - $log_filename"); 2746 error_report($file, $log_filename, $tested); 2747 } 2748 } 2749 2750 if ($valgrind && $leaked && $cfg["show"]["mem"]) { 2751 show_file_block('mem', file_get_contents($memcheck_filename)); 2752 } 2753 2754 show_result(implode('&', $restype), $tested, $tested_file, $info, $temp_filenames); 2755 2756 foreach ($restype as $type) { 2757 $PHP_FAILED_TESTS[$type . 'ED'][] = array( 2758 'name' => $file, 2759 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 2760 'output' => $output_filename, 2761 'diff' => $diff_filename, 2762 'info' => $info, 2763 ); 2764 } 2765 2766 $diff = empty($diff) ? '' : preg_replace('/\e/', '<esc>', $diff); 2767 2768 junit_mark_test_as($restype, $shortname, $tested, null, $info, $diff); 2769 2770 return $restype[0] . 'ED'; 2771} 2772 2773function comp_line($l1, $l2, $is_reg) 2774{ 2775 if ($is_reg) { 2776 return preg_match('/^' . $l1 . '$/s', $l2); 2777 } else { 2778 return !strcmp($l1, $l2); 2779 } 2780} 2781 2782function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2, $steps) 2783{ 2784 $equal = 0; 2785 2786 while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { 2787 $idx1++; 2788 $idx2++; 2789 $equal++; 2790 $steps--; 2791 } 2792 if (--$steps > 0) { 2793 $eq1 = 0; 2794 $st = $steps / 2; 2795 2796 for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) { 2797 $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st); 2798 2799 if ($eq > $eq1) { 2800 $eq1 = $eq; 2801 } 2802 } 2803 2804 $eq2 = 0; 2805 $st = $steps; 2806 2807 for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) { 2808 $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st); 2809 if ($eq > $eq2) { 2810 $eq2 = $eq; 2811 } 2812 } 2813 2814 if ($eq1 > $eq2) { 2815 $equal += $eq1; 2816 } else if ($eq2 > 0) { 2817 $equal += $eq2; 2818 } 2819 } 2820 2821 return $equal; 2822} 2823 2824function generate_array_diff($ar1, $ar2, $is_reg, $w) 2825{ 2826 $idx1 = 0; 2827 $cnt1 = @count($ar1); 2828 $idx2 = 0; 2829 $cnt2 = @count($ar2); 2830 $diff = array(); 2831 $old1 = array(); 2832 $old2 = array(); 2833 2834 while ($idx1 < $cnt1 && $idx2 < $cnt2) { 2835 2836 if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { 2837 $idx1++; 2838 $idx2++; 2839 continue; 2840 } else { 2841 2842 $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1 + 1, $idx2, $cnt1, $cnt2, 10); 2843 $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2 + 1, $cnt1, $cnt2, 10); 2844 2845 if ($c1 > $c2) { 2846 $old1[$idx1] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; 2847 } else if ($c2 > 0) { 2848 $old2[$idx2] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; 2849 } else { 2850 $old1[$idx1] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; 2851 $old2[$idx2] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; 2852 } 2853 } 2854 } 2855 2856 reset($old1); 2857 $k1 = key($old1); 2858 $l1 = -2; 2859 reset($old2); 2860 $k2 = key($old2); 2861 $l2 = -2; 2862 2863 while ($k1 !== null || $k2 !== null) { 2864 2865 if ($k1 == $l1 + 1 || $k2 === null) { 2866 $l1 = $k1; 2867 $diff[] = current($old1); 2868 $k1 = next($old1) ? key($old1) : null; 2869 } else if ($k2 == $l2 + 1 || $k1 === null) { 2870 $l2 = $k2; 2871 $diff[] = current($old2); 2872 $k2 = next($old2) ? key($old2) : null; 2873 } else if ($k1 < $k2) { 2874 $l1 = $k1; 2875 $diff[] = current($old1); 2876 $k1 = next($old1) ? key($old1) : null; 2877 } else { 2878 $l2 = $k2; 2879 $diff[] = current($old2); 2880 $k2 = next($old2) ? key($old2) : null; 2881 } 2882 } 2883 2884 while ($idx1 < $cnt1) { 2885 $diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; 2886 } 2887 2888 while ($idx2 < $cnt2) { 2889 $diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; 2890 } 2891 2892 return $diff; 2893} 2894 2895function generate_diff($wanted, $wanted_re, $output) 2896{ 2897 $w = explode("\n", $wanted); 2898 $o = explode("\n", $output); 2899 $r = is_null($wanted_re) ? $w : explode("\n", $wanted_re); 2900 $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w); 2901 2902 return implode(PHP_EOL, $diff); 2903} 2904 2905function error($message) 2906{ 2907 echo "ERROR: {$message}\n"; 2908 exit(1); 2909} 2910 2911function settings2array($settings, &$ini_settings) 2912{ 2913 foreach ($settings as $setting) { 2914 2915 if (strpos($setting, '=') !== false) { 2916 $setting = explode("=", $setting, 2); 2917 $name = trim($setting[0]); 2918 $value = trim($setting[1]); 2919 2920 if ($name == 'extension' || $name == 'zend_extension') { 2921 2922 if (!isset($ini_settings[$name])) { 2923 $ini_settings[$name] = array(); 2924 } 2925 2926 $ini_settings[$name][] = $value; 2927 2928 } else { 2929 $ini_settings[$name] = $value; 2930 } 2931 } 2932 } 2933} 2934 2935function settings2params($ini_settings) 2936{ 2937 $settings = ''; 2938 2939 foreach($ini_settings as $name => $value) { 2940 2941 if (is_array($value)) { 2942 foreach($value as $val) { 2943 $val = addslashes($val); 2944 $settings .= " -d \"$name=$val\""; 2945 } 2946 } else { 2947 if (substr(PHP_OS, 0, 3) == "WIN" && !empty($value) && $value[0] == '"') { 2948 $len = strlen($value); 2949 2950 if ($value[$len - 1] == '"') { 2951 $value[0] = "'"; 2952 $value[$len - 1] = "'"; 2953 } 2954 } else { 2955 $value = addslashes($value); 2956 } 2957 2958 $settings .= " -d \"$name=$value\""; 2959 } 2960 } 2961 2962 return $settings; 2963} 2964 2965function compute_summary() 2966{ 2967 global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results; 2968 2969 $n_total = count($test_results); 2970 $n_total += $ignored_by_ext; 2971 $sum_results = array( 2972 'PASSED' => 0, 2973 'WARNED' => 0, 2974 'SKIPPED' => 0, 2975 'FAILED' => 0, 2976 'BORKED' => 0, 2977 'LEAKED' => 0, 2978 'XFAILED' => 0, 2979 'XLEAKED' => 0 2980 ); 2981 2982 foreach ($test_results as $v) { 2983 $sum_results[$v]++; 2984 } 2985 2986 $sum_results['SKIPPED'] += $ignored_by_ext; 2987 $percent_results = array(); 2988 2989 foreach ($sum_results as $v => $n) { 2990 $percent_results[$v] = (100.0 * $n) / $n_total; 2991 } 2992} 2993 2994function get_summary($show_ext_summary, $show_html) 2995{ 2996 global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $valgrind; 2997 2998 $x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED']; 2999 3000 if ($x_total) { 3001 $x_warned = (100.0 * $sum_results['WARNED']) / $x_total; 3002 $x_failed = (100.0 * $sum_results['FAILED']) / $x_total; 3003 $x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total; 3004 $x_xleaked = (100.0 * $sum_results['XLEAKED']) / $x_total; 3005 $x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total; 3006 $x_passed = (100.0 * $sum_results['PASSED']) / $x_total; 3007 } else { 3008 $x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = $x_xleaked = 0; 3009 } 3010 3011 $summary = ''; 3012 3013 if ($show_html) { 3014 $summary .= "<pre>\n"; 3015 } 3016 3017 if ($show_ext_summary) { 3018 $summary .= ' 3019===================================================================== 3020TEST RESULT SUMMARY 3021--------------------------------------------------------------------- 3022Exts skipped : ' . sprintf('%4d', $exts_skipped) . ' 3023Exts tested : ' . sprintf('%4d', $exts_tested) . ' 3024--------------------------------------------------------------------- 3025'; 3026 } 3027 3028 $summary .= ' 3029Number of tests : ' . sprintf('%4d', $n_total) . ' ' . sprintf('%8d', $x_total); 3030 3031 if ($sum_results['BORKED']) { 3032 $summary .= ' 3033Tests borked : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------'; 3034 } 3035 3036 $summary .= ' 3037Tests skipped : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' -------- 3038Tests warned : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . ' 3039Tests failed : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed); 3040 3041 if ($sum_results['XFAILED']) { 3042 $summary .= ' 3043Expected fail : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed); 3044 } 3045 3046 if ($valgrind) { 3047 $summary .= ' 3048Tests leaked : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked); 3049 if ($sum_results['XLEAKED']) { 3050 $summary .= ' 3051Expected leak : ' . sprintf('%4d (%5.1f%%)', $sum_results['XLEAKED'], $percent_results['XLEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_xleaked); 3052 } 3053 } 3054 3055 $summary .= ' 3056Tests passed : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . ' 3057--------------------------------------------------------------------- 3058Time taken : ' . sprintf('%4d seconds', $end_time - $start_time) . ' 3059===================================================================== 3060'; 3061 $failed_test_summary = ''; 3062 3063 if (count($PHP_FAILED_TESTS['SLOW'])) { 3064 usort($PHP_FAILED_TESTS['SLOW'], function ($a, $b) { 3065 return $a['info'] < $b['info'] ? 1 : -1; 3066 }); 3067 3068 $failed_test_summary .= ' 3069===================================================================== 3070SLOW TEST SUMMARY 3071--------------------------------------------------------------------- 3072'; 3073 foreach ($PHP_FAILED_TESTS['SLOW'] as $failed_test_data) { 3074 $failed_test_summary .= sprintf('(%.3f s) ', $failed_test_data['info']) . $failed_test_data['test_name'] . "\n"; 3075 } 3076 $failed_test_summary .= "=====================================================================\n"; 3077 } 3078 3079 if (count($PHP_FAILED_TESTS['XFAILED'])) { 3080 $failed_test_summary .= ' 3081===================================================================== 3082EXPECTED FAILED TEST SUMMARY 3083--------------------------------------------------------------------- 3084'; 3085 foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) { 3086 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3087 } 3088 $failed_test_summary .= "=====================================================================\n"; 3089 } 3090 3091 if (count($PHP_FAILED_TESTS['BORKED'])) { 3092 $failed_test_summary .= ' 3093===================================================================== 3094BORKED TEST SUMMARY 3095--------------------------------------------------------------------- 3096'; 3097 foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) { 3098 $failed_test_summary .= $failed_test_data['info'] . "\n"; 3099 } 3100 3101 $failed_test_summary .= "=====================================================================\n"; 3102 } 3103 3104 if (count($PHP_FAILED_TESTS['FAILED'])) { 3105 $failed_test_summary .= ' 3106===================================================================== 3107FAILED TEST SUMMARY 3108--------------------------------------------------------------------- 3109'; 3110 foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) { 3111 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3112 } 3113 $failed_test_summary .= "=====================================================================\n"; 3114 } 3115 if (count($PHP_FAILED_TESTS['WARNED'])) { 3116 $failed_test_summary .= ' 3117===================================================================== 3118WARNED TEST SUMMARY 3119--------------------------------------------------------------------- 3120'; 3121 foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) { 3122 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3123 } 3124 3125 $failed_test_summary .= "=====================================================================\n"; 3126 } 3127 3128 if (count($PHP_FAILED_TESTS['LEAKED'])) { 3129 $failed_test_summary .= ' 3130===================================================================== 3131LEAKED TEST SUMMARY 3132--------------------------------------------------------------------- 3133'; 3134 foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) { 3135 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3136 } 3137 3138 $failed_test_summary .= "=====================================================================\n"; 3139 } 3140 3141 if (count($PHP_FAILED_TESTS['XLEAKED'])) { 3142 $failed_test_summary .= ' 3143===================================================================== 3144EXPECTED LEAK TEST SUMMARY 3145--------------------------------------------------------------------- 3146'; 3147 foreach ($PHP_FAILED_TESTS['XLEAKED'] as $failed_test_data) { 3148 $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; 3149 } 3150 3151 $failed_test_summary .= "=====================================================================\n"; 3152 } 3153 3154 if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) { 3155 $summary .= $failed_test_summary; 3156 } 3157 3158 if ($show_html) { 3159 $summary .= "</pre>"; 3160 } 3161 3162 return $summary; 3163} 3164 3165function show_start($start_time) 3166{ 3167 global $html_output, $html_file; 3168 3169 if ($html_output) { 3170 fwrite($html_file, "<h2>Time Start: " . date('Y-m-d H:i:s', $start_time) . "</h2>\n"); 3171 fwrite($html_file, "<table>\n"); 3172 } 3173 3174 echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n"; 3175} 3176 3177function show_end($end_time) 3178{ 3179 global $html_output, $html_file; 3180 3181 if ($html_output) { 3182 fwrite($html_file, "</table>\n"); 3183 fwrite($html_file, "<h2>Time End: " . date('Y-m-d H:i:s', $end_time) . "</h2>\n"); 3184 } 3185 3186 echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n"; 3187} 3188 3189function show_summary() 3190{ 3191 global $html_output, $html_file; 3192 3193 if ($html_output) { 3194 fwrite($html_file, "<hr/>\n" . get_summary(true, true)); 3195 } 3196 3197 echo get_summary(true, false); 3198} 3199 3200function show_redirect_start($tests, $tested, $tested_file) 3201{ 3202 global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; 3203 3204 if ($html_output) { 3205 fwrite($html_file, "<tr><td colspan='3'>---> $tests ($tested [$tested_file]) begin</td></tr>\n"); 3206 } 3207 3208 if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { 3209 echo "REDIRECT $tests ($tested [$tested_file]) begin\n"; 3210 } else { 3211 clear_show_test(); 3212 } 3213} 3214 3215function show_redirect_ends($tests, $tested, $tested_file) 3216{ 3217 global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; 3218 3219 if ($html_output) { 3220 fwrite($html_file, "<tr><td colspan='3'>---> $tests ($tested [$tested_file]) done</td></tr>\n"); 3221 } 3222 3223 if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { 3224 echo "REDIRECT $tests ($tested [$tested_file]) done\n"; 3225 } else { 3226 clear_show_test(); 3227 } 3228} 3229 3230function show_test($test_idx, $shortname) 3231{ 3232 global $test_cnt; 3233 global $line_length; 3234 3235 $str = "TEST $test_idx/$test_cnt [$shortname]\r"; 3236 $line_length = strlen($str); 3237 echo $str; 3238 flush(); 3239} 3240 3241function clear_show_test() { 3242 global $line_length; 3243 // Parallel testing 3244 global $workerID; 3245 3246 if (!$workerID) { 3247 // Write over the last line to avoid random trailing chars on next echo 3248 echo str_repeat(" ", $line_length), "\r"; 3249 } 3250} 3251 3252function parse_conflicts(string $text) : array { 3253 // Strip comments 3254 $text = preg_replace('/#.*/', '', $text); 3255 return array_map('trim', explode("\n", trim($text))); 3256} 3257 3258function show_result($result, $tested, $tested_file, $extra = '', $temp_filenames = null) 3259{ 3260 global $html_output, $html_file, $temp_target, $temp_urlbase, $line_length, $SHOW_ONLY_GROUPS; 3261 3262 if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) { 3263 echo "$result $tested [$tested_file] $extra\n"; 3264 } else if (!$SHOW_ONLY_GROUPS) { 3265 clear_show_test(); 3266 } 3267 3268 if ($html_output) { 3269 3270 if (isset($temp_filenames['file']) && file_exists($temp_filenames['file'])) { 3271 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['file']); 3272 $tested = "<a href='$url'>$tested</a>"; 3273 } 3274 3275 if (isset($temp_filenames['skip']) && file_exists($temp_filenames['skip'])) { 3276 3277 if (empty($extra)) { 3278 $extra = "skipif"; 3279 } 3280 3281 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['skip']); 3282 $extra = "<a href='$url'>$extra</a>"; 3283 3284 } else if (empty($extra)) { 3285 $extra = " "; 3286 } 3287 3288 if (isset($temp_filenames['diff']) && file_exists($temp_filenames['diff'])) { 3289 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['diff']); 3290 $diff = "<a href='$url'>diff</a>"; 3291 } else { 3292 $diff = " "; 3293 } 3294 3295 if (isset($temp_filenames['mem']) && file_exists($temp_filenames['mem'])) { 3296 $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['mem']); 3297 $mem = "<a href='$url'>leaks</a>"; 3298 } else { 3299 $mem = " "; 3300 } 3301 3302 fwrite( 3303 $html_file, 3304 "<tr>" . 3305 "<td>$result</td>" . 3306 "<td>$tested</td>" . 3307 "<td>$extra</td>" . 3308 "<td>$diff</td>" . 3309 "<td>$mem</td>" . 3310 "</tr>\n" 3311 ); 3312 } 3313} 3314 3315function junit_init() 3316{ 3317 // Check whether a junit log is wanted. 3318 global $workerID; 3319 $JUNIT = getenv('TEST_PHP_JUNIT'); 3320 if (empty($JUNIT)) { 3321 $GLOBALS['JUNIT'] = false; 3322 return; 3323 } 3324 if ($workerID) { 3325 $fp = null; 3326 } else if (!$fp = fopen($JUNIT, 'w')) { 3327 error("Failed to open $JUNIT for writing."); 3328 } 3329 $GLOBALS['JUNIT'] = array( 3330 'fp' => $fp, 3331 'name' => 'PHP', 3332 'test_total' => 0, 3333 'test_pass' => 0, 3334 'test_fail' => 0, 3335 'test_error' => 0, 3336 'test_skip' => 0, 3337 'test_warn' => 0, 3338 'execution_time' => 0, 3339 'suites' => array(), 3340 'files' => array() 3341 ); 3342} 3343 3344function junit_save_xml() 3345{ 3346 global $JUNIT; 3347 if (!junit_enabled()) return; 3348 3349 $xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>' . PHP_EOL; 3350 $xml .= sprintf( 3351 '<testsuites name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL, 3352 $JUNIT['name'], 3353 $JUNIT['test_total'], 3354 $JUNIT['test_fail'], 3355 $JUNIT['test_error'], 3356 $JUNIT['test_skip'], 3357 $JUNIT['execution_time'] 3358 ); 3359 $xml .= junit_get_suite_xml(); 3360 $xml .= '</testsuites>'; 3361 fwrite($JUNIT['fp'], $xml); 3362} 3363 3364function junit_get_suite_xml($suite_name = '') 3365{ 3366 global $JUNIT; 3367 3368 $result = ""; 3369 3370 foreach ($JUNIT['suites'] as $suite_name => $suite) { 3371 $result .= sprintf( 3372 '<testsuite name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL, 3373 $suite['name'], 3374 $suite['test_total'], 3375 $suite['test_fail'], 3376 $suite['test_error'], 3377 $suite['test_skip'], 3378 $suite['execution_time'] 3379 ); 3380 3381 if (!empty($suite_name)) { 3382 foreach ($suite['files'] as $file) { 3383 $result .= $JUNIT['files'][$file]['xml']; 3384 } 3385 } 3386 3387 $result .= '</testsuite>' . PHP_EOL; 3388 } 3389 3390 return $result; 3391} 3392 3393function junit_enabled() 3394{ 3395 global $JUNIT; 3396 return !empty($JUNIT); 3397} 3398 3399/** 3400 * @param array|string $type 3401 * @param string $file_name 3402 * @param string $test_name 3403 * @param int|string $time 3404 * @param string $message 3405 * @param string $details 3406 * @return void 3407 */ 3408function junit_mark_test_as($type, $file_name, $test_name, $time = null, $message = '', $details = '') 3409{ 3410 global $JUNIT; 3411 if (!junit_enabled()) return; 3412 3413 $suite = junit_get_suitename_for($file_name); 3414 3415 junit_suite_record($suite, 'test_total'); 3416 3417 $time = $time ?? junit_get_timer($file_name); 3418 junit_suite_record($suite, 'execution_time', $time); 3419 3420 $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8'); 3421 $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) { 3422 return sprintf('[[0x%02x]]', ord($c[0])); 3423 }, $escaped_details); 3424 $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); 3425 3426 $escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES); 3427 $JUNIT['files'][$file_name]['xml'] = "<testcase name='$escaped_test_name' time='$time'>\n"; 3428 3429 if (is_array($type)) { 3430 $output_type = $type[0] . 'ED'; 3431 $temp = array_intersect(array('XFAIL', 'XLEAK', 'FAIL', 'WARN'), $type); 3432 $type = reset($temp); 3433 } else { 3434 $output_type = $type . 'ED'; 3435 } 3436 3437 if ('PASS' == $type || 'XFAIL' == $type || 'XLEAK' == $type) { 3438 junit_suite_record($suite, 'test_pass'); 3439 } elseif ('BORK' == $type) { 3440 junit_suite_record($suite, 'test_error'); 3441 $JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'/>\n"; 3442 } elseif ('SKIP' == $type) { 3443 junit_suite_record($suite, 'test_skip'); 3444 $JUNIT['files'][$file_name]['xml'] .= "<skipped>$escaped_message</skipped>\n"; 3445 } elseif ('WARN' == $type) { 3446 junit_suite_record($suite, 'test_warn'); 3447 $JUNIT['files'][$file_name]['xml'] .= "<warning>$escaped_message</warning>\n"; 3448 } elseif ('FAIL' == $type) { 3449 junit_suite_record($suite, 'test_fail'); 3450 $JUNIT['files'][$file_name]['xml'] .= "<failure type='$output_type' message='$escaped_message'>$escaped_details</failure>\n"; 3451 } else { 3452 junit_suite_record($suite, 'test_error'); 3453 $JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'>$escaped_details</error>\n"; 3454 } 3455 3456 $JUNIT['files'][$file_name]['xml'] .= "</testcase>\n"; 3457 3458} 3459 3460function junit_suite_record($suite, $param, $value = 1) 3461{ 3462 global $JUNIT; 3463 3464 $JUNIT[$param] += $value; 3465 $JUNIT['suites'][$suite][$param] += $value; 3466} 3467 3468function junit_get_timer($file_name) 3469{ 3470 global $JUNIT; 3471 if (!junit_enabled()) return 0; 3472 3473 if (isset($JUNIT['files'][$file_name]['total'])) { 3474 return number_format($JUNIT['files'][$file_name]['total'], 4); 3475 } 3476 3477 return 0; 3478} 3479 3480function junit_start_timer($file_name) 3481{ 3482 global $JUNIT; 3483 if (!junit_enabled()) return; 3484 3485 if (!isset($JUNIT['files'][$file_name]['start'])) { 3486 $JUNIT['files'][$file_name]['start'] = microtime(true); 3487 3488 $suite = junit_get_suitename_for($file_name); 3489 junit_init_suite($suite); 3490 $JUNIT['suites'][$suite]['files'][$file_name] = $file_name; 3491 } 3492} 3493 3494function junit_get_suitename_for($file_name) 3495{ 3496 return junit_path_to_classname(dirname($file_name)); 3497} 3498 3499function junit_path_to_classname($file_name) 3500{ 3501 global $JUNIT; 3502 3503 if (!junit_enabled()) return ''; 3504 3505 $ret = $JUNIT['name']; 3506 $_tmp = array(); 3507 3508 // lookup whether we're in the PHP source checkout 3509 $max = 5; 3510 if (is_file($file_name)) { 3511 $dir = dirname(realpath($file_name)); 3512 } else { 3513 $dir = realpath($file_name); 3514 } 3515 do { 3516 array_unshift($_tmp, basename($dir)); 3517 $chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h"; 3518 $dir = dirname($dir); 3519 } while (!file_exists($chk) && --$max > 0); 3520 if (file_exists($chk)) { 3521 if ($max) { 3522 array_shift($_tmp); 3523 } 3524 foreach ($_tmp as $p) { 3525 $ret .= "." . preg_replace(",[^a-z0-9]+,i", ".", $p); 3526 } 3527 return $ret; 3528 } 3529 3530 return $JUNIT['name'] . '.' . str_replace(array(DIRECTORY_SEPARATOR, '-'), '.', $file_name); 3531} 3532 3533function junit_init_suite($suite_name) 3534{ 3535 global $JUNIT; 3536 if (!junit_enabled()) return; 3537 3538 if (!empty($JUNIT['suites'][$suite_name])) { 3539 return; 3540 } 3541 3542 $JUNIT['suites'][$suite_name] = array( 3543 'name' => $suite_name, 3544 'test_total' => 0, 3545 'test_pass' => 0, 3546 'test_fail' => 0, 3547 'test_error' => 0, 3548 'test_skip' => 0, 3549 'test_warn' => 0, 3550 'files' => array(), 3551 'execution_time' => 0, 3552 ); 3553} 3554 3555function junit_finish_timer($file_name) 3556{ 3557 global $JUNIT; 3558 if (!junit_enabled()) return; 3559 3560 if (!isset($JUNIT['files'][$file_name]['start'])) { 3561 error("Timer for $file_name was not started!"); 3562 } 3563 3564 if (!isset($JUNIT['files'][$file_name]['total'])) { 3565 $JUNIT['files'][$file_name]['total'] = 0; 3566 } 3567 3568 $start = $JUNIT['files'][$file_name]['start']; 3569 $JUNIT['files'][$file_name]['total'] += microtime(true) - $start; 3570 unset($JUNIT['files'][$file_name]['start']); 3571} 3572 3573function junit_merge_results($junit) 3574{ 3575 global $JUNIT; 3576 $JUNIT['test_total'] += $junit['test_total']; 3577 $JUNIT['test_pass'] += $junit['test_pass']; 3578 $JUNIT['test_fail'] += $junit['test_fail']; 3579 $JUNIT['test_error'] += $junit['test_error']; 3580 $JUNIT['test_skip'] += $junit['test_skip']; 3581 $JUNIT['test_warn'] += $junit['test_warn']; 3582 $JUNIT['execution_time'] += $junit['execution_time']; 3583 $JUNIT['files'] += $junit['files']; 3584 foreach ($junit['suites'] as $name => $suite) { 3585 if (!isset($JUNIT['suites'][$name])) { 3586 $JUNIT['suites'][$name] = $suite; 3587 continue; 3588 } 3589 3590 $SUITE =& $JUNIT['suites'][$name]; 3591 $SUITE['test_total'] += $suite['test_total']; 3592 $SUITE['test_pass'] += $suite['test_pass']; 3593 $SUITE['test_fail'] += $suite['test_fail']; 3594 $SUITE['test_error'] += $suite['test_error']; 3595 $SUITE['test_skip'] += $suite['test_skip']; 3596 $SUITE['test_warn'] += $suite['test_warn']; 3597 $SUITE['execution_time'] += $suite['execution_time']; 3598 $SUITE['files'] += $suite['files']; 3599 } 3600} 3601 3602class RuntestsValgrind 3603{ 3604 protected $version = ''; 3605 protected $header = ''; 3606 protected $version_3_3_0 = false; 3607 protected $version_3_8_0 = false; 3608 protected $tool = null; 3609 3610 public function getVersion() 3611 { 3612 return $this->version; 3613 } 3614 3615 public function getHeader() 3616 { 3617 return $this->header; 3618 } 3619 3620 public function __construct(array $environment, string $tool = 'memcheck') 3621 { 3622 $this->tool = $tool; 3623 $header = system_with_timeout("valgrind --tool={$this->tool} --version", $environment); 3624 if (!$header) { 3625 error("Valgrind returned no version info for {$this->tool}, cannot proceed.\n". 3626 "Please check if Valgrind is installed and the tool is named correctly."); 3627 } 3628 $count = 0; 3629 $version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $header, 1, $count); 3630 if ($count != 1) { 3631 error("Valgrind returned invalid version info (\"{$header}\") for {$this->tool}, cannot proceed."); 3632 } 3633 $this->version = $version; 3634 $this->header = sprintf( 3635 "%s (%s)", trim($header), $this->tool); 3636 $this->version_3_3_0 = version_compare($version, '3.3.0', '>='); 3637 $this->version_3_8_0 = version_compare($version, '3.8.0', '>='); 3638 } 3639 3640 public function wrapCommand($cmd, $memcheck_filename, $check_all) 3641 { 3642 $vcmd = "valgrind -q --tool={$this->tool} --trace-children=yes"; 3643 if ($check_all) { 3644 $vcmd .= ' --smc-check=all'; 3645 } 3646 3647 /* --vex-iropt-register-updates=allregs-at-mem-access is necessary for phpdbg watchpoint tests */ 3648 if ($this->version_3_8_0) { 3649 /* valgrind 3.3.0+ doesn't have --log-file-exactly option */ 3650 return "$vcmd --vex-iropt-register-updates=allregs-at-mem-access --log-file=$memcheck_filename $cmd"; 3651 } elseif ($this->version_3_3_0) { 3652 return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file=$memcheck_filename $cmd"; 3653 } else { 3654 return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file-exactly=$memcheck_filename $cmd"; 3655 } 3656 } 3657} 3658 3659main(); 3660