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