mirror of
https://github.com/php/php-src.git
synced 2026-03-24 16:22:37 +01:00
run-tests.php: move JUnit stuff into a class
This is part one of my work that was announced at https://externals.io/message/110391 Closes GH-6671.
This commit is contained in:
committed by
Nikita Popov
parent
99a1bc0881
commit
19680f886f
605
run-tests.php
605
run-tests.php
@@ -25,10 +25,16 @@
|
||||
|
||||
/* $Id$ */
|
||||
|
||||
/* Temporary variables while this file is being refactored. */
|
||||
/** @var ?JUnit */
|
||||
$junit = null;
|
||||
|
||||
/* End temporary variables. */
|
||||
|
||||
/* Let there be no top-level code beyond this point:
|
||||
* Only functions and classes, thanks!
|
||||
*
|
||||
* Minimum required PHP version: 7.1.0
|
||||
* Minimum required PHP version: 7.4.0
|
||||
*/
|
||||
|
||||
function show_usage(): void
|
||||
@@ -156,6 +162,10 @@ function main(): void
|
||||
global $workers, $workerID;
|
||||
global $context_line_count;
|
||||
|
||||
// Temporary for the duration of refactoring
|
||||
/** @var JUnit */
|
||||
global $junit;
|
||||
|
||||
define('IS_WINDOWS', substr(PHP_OS, 0, 3) == "WIN");
|
||||
|
||||
$workerID = 0;
|
||||
@@ -247,7 +257,7 @@ function main(): void
|
||||
$DETAILED = 0;
|
||||
}
|
||||
|
||||
junit_init();
|
||||
$junit = new JUnit($environment, $workerID);
|
||||
|
||||
if (getenv('SHOW_ONLY_GROUPS')) {
|
||||
$SHOW_ONLY_GROUPS = explode(",", getenv('SHOW_ONLY_GROUPS'));
|
||||
@@ -772,7 +782,7 @@ function main(): void
|
||||
save_or_mail_results();
|
||||
}
|
||||
|
||||
junit_save_xml();
|
||||
$junit->saveXML();
|
||||
if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' &&
|
||||
($sum_results['FAILED'] || $sum_results['LEAKED'])) {
|
||||
exit(1);
|
||||
@@ -1364,6 +1374,8 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v
|
||||
{
|
||||
global $workers, $test_idx, $test_cnt, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $SHOW_ONLY_GROUPS, $valgrind;
|
||||
|
||||
global $junit;
|
||||
|
||||
// The PHP binary running run-tests.php, and run-tests.php itself
|
||||
// This PHP executable is *not* necessarily the same as the tested version
|
||||
$thisPHP = PHP_BINARY;
|
||||
@@ -1562,9 +1574,7 @@ escape:
|
||||
}
|
||||
}
|
||||
}
|
||||
if (junit_enabled()) {
|
||||
junit_merge_results($message["junit"]);
|
||||
}
|
||||
$junit->mergeResults($message["junit"]);
|
||||
// no break
|
||||
case "ready":
|
||||
// Schedule sequential tests only once we are down to one worker.
|
||||
@@ -1704,6 +1714,8 @@ function run_worker(): void
|
||||
{
|
||||
global $workerID, $workerSock;
|
||||
|
||||
global $junit;
|
||||
|
||||
$sockUri = getenv("TEST_PHP_URI");
|
||||
|
||||
$workerSock = stream_socket_client($sockUri, $_, $_, 5) or error("Couldn't connect to $sockUri");
|
||||
@@ -1750,9 +1762,9 @@ function run_worker(): void
|
||||
run_all_tests($command["test_files"], $command["env"], $command["redir_tested"]);
|
||||
send_message($workerSock, [
|
||||
"type" => "tests_finished",
|
||||
"junit" => junit_enabled() ? $GLOBALS['JUNIT'] : null,
|
||||
"junit" => $junit->isEnabled() ? $junit : null,
|
||||
]);
|
||||
junit_init();
|
||||
//junit_init(); TODO is this needed?
|
||||
break;
|
||||
default:
|
||||
send_message($workerSock, [
|
||||
@@ -1789,9 +1801,11 @@ function show_file_block(string $file, string $block, ?string $section = null):
|
||||
}
|
||||
|
||||
function skip_test(string $tested, string $tested_file, string $shortname, string $reason) {
|
||||
global $junit;
|
||||
|
||||
show_result('SKIP', $tested, $tested_file, "reason: $reason");
|
||||
junit_init_suite(junit_get_suitename_for($shortname));
|
||||
junit_mark_test_as('SKIP', $shortname, $tested, 0, $reason);
|
||||
$junit->initSuite($junit->getSuiteName($shortname));
|
||||
$junit->markTestAs('SKIP', $shortname, $tested, 0, $reason);
|
||||
return 'SKIPPED';
|
||||
}
|
||||
|
||||
@@ -1814,6 +1828,11 @@ function run_test(string $php, $file, array $env): string
|
||||
global $num_repeats;
|
||||
// Parallel testing
|
||||
global $workerID;
|
||||
|
||||
// Temporary
|
||||
/** @var JUnit */
|
||||
global $junit;
|
||||
|
||||
$temp_filenames = null;
|
||||
$org_file = $file;
|
||||
|
||||
@@ -1963,7 +1982,7 @@ TEST $file
|
||||
'info' => "$bork_info [$file]",
|
||||
];
|
||||
|
||||
junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info);
|
||||
$junit->markTestAs('BORK', $shortname, $tested_file, 0, $bork_info);
|
||||
return 'BORKED';
|
||||
}
|
||||
|
||||
@@ -2208,14 +2227,14 @@ TEST $file
|
||||
$env['ZEND_DONT_UNLOAD_MODULES'] = 1;
|
||||
}
|
||||
|
||||
junit_start_timer($shortname);
|
||||
$junit->startTimer($shortname);
|
||||
|
||||
$startTime = microtime(true);
|
||||
$output = system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=1 -d display_startup_errors=0 \"$test_skipif\"", $env);
|
||||
$output = trim($output);
|
||||
$time = microtime(true) - $startTime;
|
||||
|
||||
junit_finish_timer($shortname);
|
||||
$junit->stopTimer($shortname);
|
||||
|
||||
if ($time > $slow_min_ms / 1000) {
|
||||
$PHP_FAILED_TESTS['SLOW'][] = [
|
||||
@@ -2243,7 +2262,7 @@ TEST $file
|
||||
}
|
||||
|
||||
$message = !empty($m[1]) ? $m[1] : '';
|
||||
junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
|
||||
$junit->markTestAs('SKIP', $shortname, $tested, null, $message);
|
||||
return 'SKIPPED';
|
||||
}
|
||||
|
||||
@@ -2265,7 +2284,7 @@ TEST $file
|
||||
'info' => "$output [$file]",
|
||||
];
|
||||
|
||||
junit_mark_test_as('BORK', $shortname, $tested, null, $output);
|
||||
$junit->markTestAs('BORK', $shortname, $tested, null, $output);
|
||||
return 'BORKED';
|
||||
}
|
||||
}
|
||||
@@ -2276,7 +2295,7 @@ TEST $file
|
||||
|| array_key_exists("DEFLATE_POST", $section_text))) {
|
||||
$message = "ext/zlib required";
|
||||
show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames);
|
||||
junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
|
||||
$junit->markTestAs('SKIP', $shortname, $tested, null, $message);
|
||||
return 'SKIPPED';
|
||||
}
|
||||
|
||||
@@ -2316,7 +2335,7 @@ TEST $file
|
||||
// a redirected test never fails
|
||||
$IN_REDIRECT = false;
|
||||
|
||||
junit_mark_test_as('PASS', $shortname, $tested);
|
||||
$junit->markTestAs('PASS', $shortname, $tested);
|
||||
return 'REDIR';
|
||||
} else {
|
||||
$bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory.";
|
||||
@@ -2346,7 +2365,7 @@ TEST $file
|
||||
'info' => "$bork_info [$file]",
|
||||
];
|
||||
|
||||
junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info);
|
||||
$junit->markTestAs('BORK', $shortname, $tested, null, $bork_info);
|
||||
|
||||
return 'BORKED';
|
||||
}
|
||||
@@ -2417,7 +2436,7 @@ TEST $file
|
||||
$env['REQUEST_METHOD'] = 'POST';
|
||||
|
||||
if (empty($request)) {
|
||||
junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
|
||||
$junit->markTestAs('BORK', $shortname, $tested, null, 'empty $request');
|
||||
return 'BORKED';
|
||||
}
|
||||
|
||||
@@ -2448,7 +2467,7 @@ TEST $file
|
||||
$env['REQUEST_METHOD'] = 'PUT';
|
||||
|
||||
if (empty($request)) {
|
||||
junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
|
||||
$junit->markTestAs('BORK', $shortname, $tested, null, 'empty $request');
|
||||
return 'BORKED';
|
||||
}
|
||||
|
||||
@@ -2525,13 +2544,13 @@ COMMAND $cmd
|
||||
";
|
||||
}
|
||||
|
||||
junit_start_timer($shortname);
|
||||
$junit->startTimer($shortname);
|
||||
$hrtime = hrtime();
|
||||
$startTime = $hrtime[0] * 1000000000 + $hrtime[1];
|
||||
|
||||
$out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr);
|
||||
|
||||
junit_finish_timer($shortname);
|
||||
$junit->stopTimer($shortname);
|
||||
$hrtime = hrtime();
|
||||
$time = $hrtime[0] * 1000000000 + $hrtime[1] - $startTime;
|
||||
if ($time >= $slow_min_ms * 1000000) {
|
||||
@@ -2720,7 +2739,7 @@ COMMAND $cmd
|
||||
$info = " (warn: XLEAK section but test passes)";
|
||||
} else {
|
||||
show_result("PASS", $tested, $tested_file, '', $temp_filenames);
|
||||
junit_mark_test_as('PASS', $shortname, $tested);
|
||||
$junit->markTestAs('PASS', $shortname, $tested);
|
||||
return 'PASSED';
|
||||
}
|
||||
}
|
||||
@@ -2748,7 +2767,7 @@ COMMAND $cmd
|
||||
$info = " (warn: XLEAK section but test passes)";
|
||||
} else {
|
||||
show_result("PASS", $tested, $tested_file, '', $temp_filenames);
|
||||
junit_mark_test_as('PASS', $shortname, $tested);
|
||||
$junit->markTestAs('PASS', $shortname, $tested);
|
||||
return 'PASSED';
|
||||
}
|
||||
}
|
||||
@@ -2870,7 +2889,7 @@ SH;
|
||||
|
||||
$diff = empty($diff) ? '' : preg_replace('/\e/', '<esc>', $diff);
|
||||
|
||||
junit_mark_test_as($restype, $shortname, $tested, null, $info, $diff);
|
||||
$junit->markTestAs($restype, $shortname, $tested, null, $info, $diff);
|
||||
|
||||
return $restype[0] . 'ED';
|
||||
}
|
||||
@@ -3413,246 +3432,14 @@ function show_result(
|
||||
|
||||
}
|
||||
|
||||
function junit_init(): void
|
||||
class JUnit
|
||||
{
|
||||
// Check whether a junit log is wanted.
|
||||
global $workerID;
|
||||
$JUNIT = getenv('TEST_PHP_JUNIT');
|
||||
if (empty($JUNIT)) {
|
||||
$GLOBALS['JUNIT'] = false;
|
||||
return;
|
||||
}
|
||||
if ($workerID) {
|
||||
$fp = null;
|
||||
} elseif (!$fp = fopen($JUNIT, 'w')) {
|
||||
error("Failed to open $JUNIT for writing.");
|
||||
}
|
||||
$GLOBALS['JUNIT'] = [
|
||||
'fp' => $fp,
|
||||
'name' => 'PHP',
|
||||
'test_total' => 0,
|
||||
'test_pass' => 0,
|
||||
'test_fail' => 0,
|
||||
'test_error' => 0,
|
||||
'test_skip' => 0,
|
||||
'test_warn' => 0,
|
||||
'execution_time' => 0,
|
||||
'suites' => [],
|
||||
'files' => []
|
||||
];
|
||||
}
|
||||
private bool $enabled = true;
|
||||
private $fp = null;
|
||||
private array $suites = [];
|
||||
private array $rootSuite = self::EMPTY_SUITE + ['name' => 'php'];
|
||||
|
||||
function junit_save_xml(): void
|
||||
{
|
||||
global $JUNIT;
|
||||
if (!junit_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>' . PHP_EOL;
|
||||
$xml .= sprintf(
|
||||
'<testsuites name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
|
||||
$JUNIT['name'],
|
||||
$JUNIT['test_total'],
|
||||
$JUNIT['test_fail'],
|
||||
$JUNIT['test_error'],
|
||||
$JUNIT['test_skip'],
|
||||
$JUNIT['execution_time']
|
||||
);
|
||||
$xml .= junit_get_suite_xml();
|
||||
$xml .= '</testsuites>';
|
||||
fwrite($JUNIT['fp'], $xml);
|
||||
}
|
||||
|
||||
function junit_get_suite_xml(string $suite_name = ''): string
|
||||
{
|
||||
global $JUNIT;
|
||||
|
||||
$result = "";
|
||||
|
||||
foreach ($JUNIT['suites'] as $suite_name => $suite) {
|
||||
$result .= sprintf(
|
||||
'<testsuite name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
|
||||
$suite['name'],
|
||||
$suite['test_total'],
|
||||
$suite['test_fail'],
|
||||
$suite['test_error'],
|
||||
$suite['test_skip'],
|
||||
$suite['execution_time']
|
||||
);
|
||||
|
||||
if (!empty($suite_name)) {
|
||||
foreach ($suite['files'] as $file) {
|
||||
$result .= $JUNIT['files'][$file]['xml'];
|
||||
}
|
||||
}
|
||||
|
||||
$result .= '</testsuite>' . PHP_EOL;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function junit_enabled(): bool
|
||||
{
|
||||
global $JUNIT;
|
||||
return !empty($JUNIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $type
|
||||
*/
|
||||
function junit_mark_test_as(
|
||||
$type,
|
||||
string $file_name,
|
||||
string $test_name,
|
||||
?int $time = null,
|
||||
string $message = '',
|
||||
string $details = ''
|
||||
): void {
|
||||
global $JUNIT;
|
||||
if (!junit_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suite = junit_get_suitename_for($file_name);
|
||||
|
||||
junit_suite_record($suite, 'test_total');
|
||||
|
||||
$time = $time ?? junit_get_timer($file_name);
|
||||
junit_suite_record($suite, 'execution_time', $time);
|
||||
|
||||
$escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8');
|
||||
$escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function (array $c): string {
|
||||
return sprintf('[[0x%02x]]', ord($c[0]));
|
||||
}, $escaped_details);
|
||||
$escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES);
|
||||
$JUNIT['files'][$file_name]['xml'] = "<testcase name='$escaped_test_name' time='$time'>\n";
|
||||
|
||||
if (is_array($type)) {
|
||||
$output_type = $type[0] . 'ED';
|
||||
$temp = array_intersect(['XFAIL', 'XLEAK', 'FAIL', 'WARN'], $type);
|
||||
$type = reset($temp);
|
||||
} else {
|
||||
$output_type = $type . 'ED';
|
||||
}
|
||||
|
||||
if ('PASS' == $type || 'XFAIL' == $type || 'XLEAK' == $type) {
|
||||
junit_suite_record($suite, 'test_pass');
|
||||
} elseif ('BORK' == $type) {
|
||||
junit_suite_record($suite, 'test_error');
|
||||
$JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'/>\n";
|
||||
} elseif ('SKIP' == $type) {
|
||||
junit_suite_record($suite, 'test_skip');
|
||||
$JUNIT['files'][$file_name]['xml'] .= "<skipped>$escaped_message</skipped>\n";
|
||||
} elseif ('WARN' == $type) {
|
||||
junit_suite_record($suite, 'test_warn');
|
||||
$JUNIT['files'][$file_name]['xml'] .= "<warning>$escaped_message</warning>\n";
|
||||
} elseif ('FAIL' == $type) {
|
||||
junit_suite_record($suite, 'test_fail');
|
||||
$JUNIT['files'][$file_name]['xml'] .= "<failure type='$output_type' message='$escaped_message'>$escaped_details</failure>\n";
|
||||
} else {
|
||||
junit_suite_record($suite, 'test_error');
|
||||
$JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'>$escaped_details</error>\n";
|
||||
}
|
||||
|
||||
$JUNIT['files'][$file_name]['xml'] .= "</testcase>\n";
|
||||
}
|
||||
|
||||
function junit_suite_record(string $suite, string $param, int $value = 1): void
|
||||
{
|
||||
global $JUNIT;
|
||||
|
||||
$JUNIT[$param] += $value;
|
||||
$JUNIT['suites'][$suite][$param] += $value;
|
||||
}
|
||||
|
||||
function junit_get_timer(string $file_name): int
|
||||
{
|
||||
global $JUNIT;
|
||||
if (!junit_enabled()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isset($JUNIT['files'][$file_name]['total'])) {
|
||||
return number_format($JUNIT['files'][$file_name]['total'], 4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function junit_start_timer(string $file_name): void
|
||||
{
|
||||
global $JUNIT;
|
||||
if (!junit_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($JUNIT['files'][$file_name]['start'])) {
|
||||
$JUNIT['files'][$file_name]['start'] = microtime(true);
|
||||
|
||||
$suite = junit_get_suitename_for($file_name);
|
||||
junit_init_suite($suite);
|
||||
$JUNIT['suites'][$suite]['files'][$file_name] = $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
function junit_get_suitename_for(string $file_name): string
|
||||
{
|
||||
return junit_path_to_classname(dirname($file_name));
|
||||
}
|
||||
|
||||
function junit_path_to_classname(string $file_name): string
|
||||
{
|
||||
global $JUNIT;
|
||||
|
||||
if (!junit_enabled()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ret = $JUNIT['name'];
|
||||
$_tmp = [];
|
||||
|
||||
// lookup whether we're in the PHP source checkout
|
||||
$max = 5;
|
||||
if (is_file($file_name)) {
|
||||
$dir = dirname(realpath($file_name));
|
||||
} else {
|
||||
$dir = realpath($file_name);
|
||||
}
|
||||
do {
|
||||
array_unshift($_tmp, basename($dir));
|
||||
$chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h";
|
||||
$dir = dirname($dir);
|
||||
} while (!file_exists($chk) && --$max > 0);
|
||||
if (file_exists($chk)) {
|
||||
if ($max) {
|
||||
array_shift($_tmp);
|
||||
}
|
||||
foreach ($_tmp as $p) {
|
||||
$ret .= "." . preg_replace(",[^a-z0-9]+,i", ".", $p);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return $JUNIT['name'] . '.' . str_replace([DIRECTORY_SEPARATOR, '-'], '.', $file_name);
|
||||
}
|
||||
|
||||
function junit_init_suite(string $suite_name): void
|
||||
{
|
||||
global $JUNIT;
|
||||
if (!junit_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($JUNIT['suites'][$suite_name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$JUNIT['suites'][$suite_name] = [
|
||||
'name' => $suite_name,
|
||||
private const EMPTY_SUITE = [
|
||||
'test_total' => 0,
|
||||
'test_pass' => 0,
|
||||
'test_fail' => 0,
|
||||
@@ -3662,54 +3449,264 @@ function junit_init_suite(string $suite_name): void
|
||||
'files' => [],
|
||||
'execution_time' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
function junit_finish_timer(string $file_name): void
|
||||
{
|
||||
global $JUNIT;
|
||||
if (!junit_enabled()) {
|
||||
return;
|
||||
public function __construct(array $env, int $workerID)
|
||||
{
|
||||
// Check whether a junit log is wanted.
|
||||
$fileName = $env['TEST_PHP_JUNIT'] ?? null;
|
||||
if (empty($fileName)) {
|
||||
$this->enabled = false;
|
||||
return;
|
||||
}
|
||||
if (!$workerID && !$this->fp = fopen($fileName, 'w')) {
|
||||
throw new Exception("Failed to open $fileName for writing.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($JUNIT['files'][$file_name]['start'])) {
|
||||
error("Timer for $file_name was not started!");
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
if (!isset($JUNIT['files'][$file_name]['total'])) {
|
||||
$JUNIT['files'][$file_name]['total'] = 0;
|
||||
}
|
||||
|
||||
$start = $JUNIT['files'][$file_name]['start'];
|
||||
$JUNIT['files'][$file_name]['total'] += microtime(true) - $start;
|
||||
unset($JUNIT['files'][$file_name]['start']);
|
||||
}
|
||||
|
||||
function junit_merge_results(array $junit): void
|
||||
{
|
||||
global $JUNIT;
|
||||
$JUNIT['test_total'] += $junit['test_total'];
|
||||
$JUNIT['test_pass'] += $junit['test_pass'];
|
||||
$JUNIT['test_fail'] += $junit['test_fail'];
|
||||
$JUNIT['test_error'] += $junit['test_error'];
|
||||
$JUNIT['test_skip'] += $junit['test_skip'];
|
||||
$JUNIT['test_warn'] += $junit['test_warn'];
|
||||
$JUNIT['execution_time'] += $junit['execution_time'];
|
||||
$JUNIT['files'] += $junit['files'];
|
||||
foreach ($junit['suites'] as $name => $suite) {
|
||||
if (!isset($JUNIT['suites'][$name])) {
|
||||
$JUNIT['suites'][$name] = $suite;
|
||||
continue;
|
||||
public function saveXML(): void
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$SUITE =& $JUNIT['suites'][$name];
|
||||
$SUITE['test_total'] += $suite['test_total'];
|
||||
$SUITE['test_pass'] += $suite['test_pass'];
|
||||
$SUITE['test_fail'] += $suite['test_fail'];
|
||||
$SUITE['test_error'] += $suite['test_error'];
|
||||
$SUITE['test_skip'] += $suite['test_skip'];
|
||||
$SUITE['test_warn'] += $suite['test_warn'];
|
||||
$SUITE['execution_time'] += $suite['execution_time'];
|
||||
$SUITE['files'] += $suite['files'];
|
||||
$xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>' . PHP_EOL;
|
||||
$xml .= sprintf(
|
||||
'<testsuites name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
|
||||
$this->rootSuite['name'],
|
||||
$this->rootSuite['test_total'],
|
||||
$this->rootSuite['test_fail'],
|
||||
$this->rootSuite['test_error'],
|
||||
$this->rootSuite['test_skip'],
|
||||
$this->rootSuite['execution_time']
|
||||
);
|
||||
$xml .= $this->getSuitesXML();
|
||||
$xml .= '</testsuites>';
|
||||
fwrite($this->fp, $xml);
|
||||
}
|
||||
|
||||
private function getSuitesXML(string $suite_name = '')
|
||||
{
|
||||
// FIXME: $suite_name gets overwritten
|
||||
$result = '';
|
||||
|
||||
foreach ($this->suites as $suite_name => $suite) {
|
||||
$result .= sprintf(
|
||||
'<testsuite name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
|
||||
$suite['name'],
|
||||
$suite['test_total'],
|
||||
$suite['test_fail'],
|
||||
$suite['test_error'],
|
||||
$suite['test_skip'],
|
||||
$suite['execution_time']
|
||||
);
|
||||
|
||||
if (!empty($suite_name)) {
|
||||
foreach ($suite['files'] as $file) {
|
||||
$result .= $this->rootSuite['files'][$file]['xml'];
|
||||
}
|
||||
}
|
||||
|
||||
$result .= '</testsuite>' . PHP_EOL;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function markTestAs(
|
||||
$type,
|
||||
string $file_name,
|
||||
string $test_name,
|
||||
?int $time = null,
|
||||
string $message = '',
|
||||
string $details = ''
|
||||
): void {
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suite = $this->getSuiteName($file_name);
|
||||
|
||||
$this->record($suite, 'test_total');
|
||||
|
||||
$time = $time ?? $this->getTimer($file_name);
|
||||
$this->record($suite, 'execution_time', $time);
|
||||
|
||||
$escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8');
|
||||
$escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) {
|
||||
return sprintf('[[0x%02x]]', ord($c[0]));
|
||||
}, $escaped_details);
|
||||
$escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES);
|
||||
$this->rootSuite['files'][$file_name]['xml'] = "<testcase name='$escaped_test_name' time='$time'>\n";
|
||||
|
||||
if (is_array($type)) {
|
||||
$output_type = $type[0] . 'ED';
|
||||
$temp = array_intersect(['XFAIL', 'XLEAK', 'FAIL', 'WARN'], $type);
|
||||
$type = reset($temp);
|
||||
} else {
|
||||
$output_type = $type . 'ED';
|
||||
}
|
||||
|
||||
if ('PASS' == $type || 'XFAIL' == $type || 'XLEAK' == $type) {
|
||||
$this->record($suite, 'test_pass');
|
||||
} elseif ('BORK' == $type) {
|
||||
$this->record($suite, 'test_error');
|
||||
$this->rootSuite['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'/>\n";
|
||||
} elseif ('SKIP' == $type) {
|
||||
$this->record($suite, 'test_skip');
|
||||
$this->rootSuite['files'][$file_name]['xml'] .= "<skipped>$escaped_message</skipped>\n";
|
||||
} elseif ('WARN' == $type) {
|
||||
$this->record($suite, 'test_warn');
|
||||
$this->rootSuite['files'][$file_name]['xml'] .= "<warning>$escaped_message</warning>\n";
|
||||
} elseif ('FAIL' == $type) {
|
||||
$this->record($suite, 'test_fail');
|
||||
$this->rootSuite['files'][$file_name]['xml'] .= "<failure type='$output_type' message='$escaped_message'>$escaped_details</failure>\n";
|
||||
} else {
|
||||
$this->record($suite, 'test_error');
|
||||
$this->rootSuite['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'>$escaped_details</error>\n";
|
||||
}
|
||||
|
||||
$this->rootSuite['files'][$file_name]['xml'] .= "</testcase>\n";
|
||||
}
|
||||
|
||||
private function record(string $suite, string $param, $value = 1): void
|
||||
{
|
||||
$this->rootSuite[$param] += $value;
|
||||
$this->suites[$suite][$param] += $value;
|
||||
}
|
||||
|
||||
private function getTimer(string $file_name)
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isset($this->rootSuite['files'][$file_name]['total'])) {
|
||||
return number_format($this->rootSuite['files'][$file_name]['total'], 4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function startTimer(string $file_name): void
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($this->rootSuite['files'][$file_name]['start'])) {
|
||||
$this->rootSuite['files'][$file_name]['start'] = microtime(true);
|
||||
|
||||
$suite = $this->getSuiteName($file_name);
|
||||
$this->initSuite($suite);
|
||||
$this->suites[$suite]['files'][$file_name] = $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
public function getSuiteName(string $file_name): string
|
||||
{
|
||||
return $this->pathToClassName(dirname($file_name));
|
||||
}
|
||||
|
||||
private function pathToClassName(string $file_name): string
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ret = $this->rootSuite['name'];
|
||||
$_tmp = [];
|
||||
|
||||
// lookup whether we're in the PHP source checkout
|
||||
$max = 5;
|
||||
if (is_file($file_name)) {
|
||||
$dir = dirname(realpath($file_name));
|
||||
} else {
|
||||
$dir = realpath($file_name);
|
||||
}
|
||||
do {
|
||||
array_unshift($_tmp, basename($dir));
|
||||
$chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h";
|
||||
$dir = dirname($dir);
|
||||
} while (!file_exists($chk) && --$max > 0);
|
||||
if (file_exists($chk)) {
|
||||
if ($max) {
|
||||
array_shift($_tmp);
|
||||
}
|
||||
foreach ($_tmp as $p) {
|
||||
$ret .= "." . preg_replace(",[^a-z0-9]+,i", ".", $p);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
return $this->rootSuite['name'] . '.' . str_replace([DIRECTORY_SEPARATOR, '-'], '.', $file_name);
|
||||
}
|
||||
|
||||
public function initSuite(string $suite_name): void
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($this->suites[$suite_name])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->suites[$suite_name] = self::EMPTY_SUITE + ['name' => $suite_name];
|
||||
}
|
||||
|
||||
public function stopTimer(string $file_name): void
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($this->rootSuite['files'][$file_name]['start'])) {
|
||||
throw new Exception("Timer for $file_name was not started!");
|
||||
}
|
||||
|
||||
if (!isset($this->rootSuite['files'][$file_name]['total'])) {
|
||||
$this->rootSuite['files'][$file_name]['total'] = 0;
|
||||
}
|
||||
|
||||
$start = $this->rootSuite['files'][$file_name]['start'];
|
||||
$this->rootSuite['files'][$file_name]['total'] += microtime(true) - $start;
|
||||
unset($this->rootSuite['files'][$file_name]['start']);
|
||||
}
|
||||
|
||||
public function mergeResults(?JUnit $other): void
|
||||
{
|
||||
if (!$this->enabled || !$other) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->mergeSuites($this->rootSuite, $other->rootSuite);
|
||||
foreach ($other->suites as $name => $suite) {
|
||||
if (!isset($this->suites[$name])) {
|
||||
$this->suites[$name] = $suite;
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->mergeSuites($this->suites[$name], $suite);
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeSuites(array &$dest, array $source): void
|
||||
{
|
||||
$dest['test_total'] += $source['test_total'];
|
||||
$dest['test_pass'] += $source['test_pass'];
|
||||
$dest['test_fail'] += $source['test_fail'];
|
||||
$dest['test_error'] += $source['test_error'];
|
||||
$dest['test_skip'] += $source['test_skip'];
|
||||
$dest['test_warn'] += $source['test_warn'];
|
||||
$dest['execution_time'] += $source['execution_time'];
|
||||
$dest['files'] += $source['files'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user