run-tests.php 137 KB

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