-
Notifications
You must be signed in to change notification settings - Fork 182
Expand file tree
/
Copy pathpatch-run-tests-windows.php
More file actions
111 lines (100 loc) · 3.79 KB
/
Copy pathpatch-run-tests-windows.php
File metadata and controls
111 lines (100 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?php
/**
* Harden the stock PHP run-tests.php for the Windows `test_c` CI job.
*
* On Windows a per-test timeout calls proc_terminate($proc, 9), which does not
* reliably kill the process tree; the wedged php.exe keeps an OS lock on the
* test's <name>.php file. run-tests.php then `goto retry`s the test, re-writes
* that file via save_text() -> file_put_contents() which returns false on the
* locked file -> error() -> exit(1), fatally aborting the whole suite mid-run.
*
* This applies two surgical, anchor-asserted patches to the copy of
* run-tests.php that phpize.bat drops into the build dir:
* 1. taskkill /T /F the timed-out process tree before proc_terminate.
* 2. Retry the save_text() write with a short backoff instead of aborting.
*
* The Windows matrix spans PHP 7.2-8.5 and run-tests.php differs between them,
* so a missing anchor is a no-op (status quo, no regression) rather than a hard
* error -- the in-extension hang watchdog covers every version regardless. Only
* a genuinely ambiguous (>1) match is treated as fatal.
*/
$path = $argv[1] ?? 'run-tests.php';
if (!is_file($path)) {
fwrite(STDERR, "patch-run-tests-windows: '$path' not found\n");
exit(1);
}
$src = file_get_contents($path);
// Match the file's existing line endings so anchors compare and patched output
// stays consistent whether the SDK ships run-tests.php with LF or CRLF.
$crlf = strpos($src, "\r\n") !== false;
/** @return void */
function apply(string &$src, string $find, string $replace, string $label): void
{
global $crlf;
if ($crlf) {
$find = str_replace("\n", "\r\n", $find);
$replace = str_replace("\n", "\r\n", $replace);
}
$count = substr_count($src, $find);
if ($count === 0) {
echo "patch-run-tests-windows: anchor '$label' not present; skipping\n";
return;
}
if ($count > 1) {
fwrite(STDERR, "patch-run-tests-windows: anchor '$label' matched $count times; ambiguous, aborting\n");
exit(1);
}
$src = str_replace($find, $replace, $src);
echo "patch-run-tests-windows: applied '$label'\n";
}
// 1. Kill the whole process tree on timeout so the test file's lock is released
// before the retry re-writes it.
apply(
$src,
<<<'PHP'
$data .= "\n ** ERROR: process timed out **\n";
proc_terminate($proc, 9);
PHP,
<<<'PHP'
$data .= "\n ** ERROR: process timed out **\n";
$dd_status = @proc_get_status($proc);
if (isset($dd_status['pid'])) {
@exec('taskkill /T /F /PID ' . (int)$dd_status['pid'] . ' 2>NUL');
}
proc_terminate($proc, 9);
PHP,
'timeout tree-kill'
);
// 2. Make the test-file write resilient to a briefly-held Windows lock so a
// retry write cannot escalate to a suite-killing exit(1).
apply(
$src,
<<<'PHP'
if ($filename_copy && $filename_copy != $filename && file_put_contents($filename_copy, $text) === false) {
error("Cannot open file '" . $filename_copy . "' (save_text)");
}
if (file_put_contents($filename, $text) === false) {
error("Cannot open file '" . $filename . "' (save_text)");
}
PHP,
<<<'PHP'
$dd_write = static function (string $f, string $t): bool {
for ($i = 0; $i < 30; $i++) {
if (file_put_contents($f, $t) !== false) {
return true;
}
usleep(100000);
}
return false;
};
if ($filename_copy && $filename_copy != $filename && !$dd_write($filename_copy, $text)) {
error("Cannot open file '" . $filename_copy . "' (save_text)");
}
if (!$dd_write($filename, $text)) {
error("Cannot open file '" . $filename . "' (save_text)");
}
PHP,
'save_text retry'
);
file_put_contents($path, $src);
echo "patch-run-tests-windows: patched $path\n";